Objectives

Introduce the fundamentals of the mean stack

Create basic Node.js application

Lab Goal:

  • Set up the project base and folder structure.
  • Set up a base server which you can run.

Project Setup

Clone the project repo: https://github.com/JameelB/node-todo

git clone https://github.com/JameelB/node-todo

or

git clone git@github.com:JameelB/node-todo.git

1. Create Folder Structure

Create the following folders and files as listed below:

- public        <!-- Contains all the files for the frontend angular application -->
  > core.js     <!-- Contains all of the angular code for the app -->
  > index.html  <!-- Main view -->
- package.json  <!-- Contains the NPM configuration to install dependencies/modules -->
- server.js     <!-- Node configuration -->

This is a simplified file structure for this tutorial. In larger application, this would be broken down further to separate duties between client and server. Checkout MEAN.io which is a good boilerplate to see best practices and how to separate file structure.

To see expected results for this step checkout the branch step-01: git checkout step-01

2. Configure package.json

The package.json holds configuration for the app. Node's package manager (NPM) will use this to install any dependencies or modules that are specified here.

Copy the following below to your package.json:

{
  "name"         : "node-todo",
  "version"      : "0.0.1",
  "description"  : "Simple todo application.",
  "main"         : "server.js",
  "author"       : "ScotchIO",
  "dependencies" : {
    "express"    : "~4.7.2",
    "mongoose"   : "~3.6.2",
    "morgan"     : "~1.2.2",
    "body-parser": "~1.5.2",
    "method-override": "~2.1.2"
    }
}

Save the file and run npm install. This will install the dependencies specified in your package.json

To see expected results for this step checkout the branch step-02: git checkout step-02

3. Node Configuration

In the package.json file, it specified that the main file would be server.js. This is the main file for our Node app and where we will configure the entire application.

In this file the following configuration needs to be done: - Database connection - Mongoose models creation - RESTful API routes definition - Frontend Angular routes definition - Set a port for the app to listen to.

// server.js

    // set up ========================
    var express  = require('express');
    var app      = express();                        // create our app w/ express
    var mongoose = require('mongoose');              // mongoose for mongodb
    var morgan = require('morgan');                  // log requests to the console (express4)
    var bodyParser = require('body-parser');         // pull information from HTML POST (express4)
    var methodOverride = require('method-override'); // simulate DELETE and PUT (express4)

    // configuration =================

    var localDbUrl = 'mongodb://localhost/meanstacktutorial'; //set your local database URL
    mongoose.connect(localDbUrl); //Connects to your local database.

    app.use(express.static(__dirname + '/public'));                 // set the static files location /public/img will be /img for users
    app.use(morgan('dev'));                                         // log every request to the console
    app.use(bodyParser.urlencoded({'extended':'true'}));            // parse application/x-www-form-urlencoded
    app.use(bodyParser.json());                                     // parse application/json
    app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
    app.use(methodOverride());

    // listen (start app with node server.js) ======================================
    app.listen(8080);
    console.log("App listening on port 8080");

Run npm run start and you should see App listening on port 8080 logged out in your terminal. If you go to your browser and go to localhost:8080/, it should not give any errors and show an empty page.

Install nodemon globally by running npm install -g nodemon. Run your application with nodemon server.js. By doing this, it will automatically monitor for file changes and restart the server.

To see expected results for this step checkout the branch step-03: git checkout step-03

Define Model and Routes

Lab Goal:

Configure the model and routes for the REST API

4. Configure model and routes for the REST API

First, we need to define a model for our data that has a text field of type string. MongoDB will automatically generate the _id field for each record created. Copy this just before the app.listen in your server.js

var Todo = mongoose.model('Todo', {
    text : String
});

Next, we need to generate our Express routes to handle our API calls. Add this to your server.js file just after the model definition above. This will make our application's API accessible from /api/todos.

app.get('/api/todos', function(req, res) {

    // use mongoose to get all todos in the database
    Todo.find(function(err, todos) {

        // if there is an error retrieving, send the error. nothing after res.send(err) will execute
        if (err)
            res.send(err)

        res.json(todos); // return all todos in JSON format
    });
});

// create todo and send back all todos after creation
app.post('/api/todos', function(req, res) {

    // create a todo, information comes from AJAX request from Angular
    Todo.create({
        text : req.body.text,
        done : false
    }, function(err, todo) {
        if (err)
            res.send(err);

        // get and return all the todos after you create another
        Todo.find(function(err, todos) {
            if (err)
                res.send(err)
            res.json(todos);
        });
    });

});

// delete a todo
app.delete('/api/todos/:todo_id', function(req, res) {
    Todo.remove({
        _id : req.params.todo_id
    }, function(err, todo) {
        if (err)
            res.send(err);

        // get and return all the todos after you create another
        Todo.find(function(err, todos) {
            if (err)
                res.send(err)
            res.json(todos);
        });
    });
});
HTTP Verb URL DESCRIPTION
GET /api/todos Get all of the todos
POST /api/todos Create a single todo
DELETE /api/todos/:todo_id Delete a single todo

To see expected results for this step checkout the branch step-04: git checkout step-04

Configure Frontend

Lab goal:

  • Configure frontend route.
  • Create an angular module, controllers and define functions to handle todos.
  • Create a view to be used by the app.

5. Configure frontend routes and core.js

Add the following route to the server.js file just before the app.listen and after the API routes.

   app.get('*', function(req, res) {
       res.sendfile('./public/index.html');
   });

This will load this view and angular will handle the changes on the frontend.

Add the following to your public/core.js file - Create an angular module.

var toDoApp = angular.module('todo-app', []);
  • Create a controller and define functions for getting, creating, and deleting todos.
function mainController($scope, $http) {
    $scope.formData = {};

    // when landing on the page, get all todos and show them
    $http.get('/api/todos')
        .success(function(data) {
            $scope.todos = data;
            console.log(data);
        })
        .error(function(data) {
            console.log('Error: ' + data);
        });

    // when submitting the add form, send the text to the node API
    $scope.createTodo = function() {
        $http.post('/api/todos', $scope.formData)
            .success(function(data) {
                $scope.formData = {}; // clear the form so our user is ready to enter another
                $scope.todos = data;
                console.log(data);
            })
            .error(function(data) {
                console.log('Error: ' + data);
            });
    };

    // delete a todo after checking it
    $scope.deleteTodo = function(id) {
        $http.delete('/api/todos/' + id)
            .success(function(data) {
                $scope.todos = data;
                console.log(data);
            })
            .error(function(data) {
                console.log('Error: ' + data);
            });
    };

}
  • Configure Frontend View to interact with Angular This should assign the Angular module and controller, initialize the page that shows all of the todos, have a form to create todos and delete todos when they are checked.

In your public/index.html file, add the following below:

<!doctype html>

<!-- ASSIGN OUR ANGULAR MODULE -->
<html ng-app="scotchTodo">
<head>
    <!-- META -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1"><!-- Optimize mobile viewport -->

    <title>Node/Angular Todo App</title>

    <!-- SCROLLS -->
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"><!-- load bootstrap -->
    <style>
        html                    { overflow-y:scroll; }
        body                    { padding-top:50px; }
        #todo-list              { margin-bottom:30px; }
    </style>

    <!-- SPELLS -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script><!-- load jquery -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script><!-- load angular -->
    <script src="core.js"></script>

</head>
<!-- SET THE CONTROLLER AND GET ALL TODOS -->
<body ng-controller="mainController">
    <div class="container">

        <!-- HEADER AND TODO COUNT -->
        <div class="jumbotron text-center">
            <h1>I'm a Todo-aholic <span class="label label-info">{{ todos.length }}</span></h1>
        </div>

        <!-- TODO LIST -->
        <div id="todo-list" class="row">
            <div class="col-sm-4 col-sm-offset-4">

                <!-- LOOP OVER THE TODOS IN $scope.todos -->
                <div class="checkbox" ng-repeat="todo in todos">
                    <label>
                        <input type="checkbox" ng-click="deleteTodo(todo._id)"> {{ todo.text }}
                    </label>
                </div>

            </div>
        </div>

        <!-- FORM TO CREATE TODOS -->
        <div id="todo-form" class="row">
            <div class="col-sm-8 col-sm-offset-2 text-center">
                <form>
                    <div class="form-group">

                        <!-- BIND THIS VALUE TO formData.text IN ANGULAR -->
                        <input type="text" class="form-control input-lg text-center" placeholder="I want to buy a puppy that will love me forever" ng-model="formData.text">
                    </div>

                    <!-- createToDo() WILL CREATE NEW TODOS -->
                    <button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add</button>
                </form>
            </div>
        </div>

    </div>

</body>
</html>

To see expected results for this step checkout the branch step-05: git checkout step-05

Lab Exercises

A set of exercises on building a simple todo web application using MongoDB, Express, AngularJS and Node.js.

The web application should allow for CRUDL operations of a dataset in the store.

Based on tutorial from scotch-io : https://scotch.io/tutorials/creating-a-single-page-todo-app-with-node-and-angular

Follow up tutorial: https://scotch.io/bar-talk/setting-up-a-mean-stack-single-page-application

Working demo on glitch: https://glitch.com/edit/#!/blossom-blinker