REST API Server with Node (Express) and Mongo

By | 02/07/2015

In previous posts, I have been using Sinatra and Flask to create a REST API for a simple todo application. Nodejs has been on my todo list for quite some time so I decided to get to create the same todo-application again in NodeJS (with the Express JS framework). Have fun following this tutorial.

Luckily, the Express framework allows us to use an Express generator, which basically will create a working Express application. Use the following command:

wim@ubuntu:~/Blog$ express Express_Todo_Mongo_API

   create : Express_Todo_Mongo_API
   create : Express_Todo_Mongo_API/package.json
   create : Express_Todo_Mongo_API/app.js
   create : Express_Todo_Mongo_API/public
   create : Express_Todo_Mongo_API/public/javascripts
   create : Express_Todo_Mongo_API/public/images
   create : Express_Todo_Mongo_API/public/stylesheets
   create : Express_Todo_Mongo_API/public/stylesheets/style.css
   create : Express_Todo_Mongo_API/routes
   create : Express_Todo_Mongo_API/routes/index.js
   create : Express_Todo_Mongo_API/routes/users.js
   create : Express_Todo_Mongo_API/views
   create : Express_Todo_Mongo_API/views/index.jade
   create : Express_Todo_Mongo_API/views/layout.jade
   create : Express_Todo_Mongo_API/views/error.jade
   create : Express_Todo_Mongo_API/bin
   create : Express_Todo_Mongo_API/bin/www

   install dependencies:
     $ cd Express_Todo_Mongo_API && npm install

   run the app:
     $ DEBUG=Express_Todo_Mongo_API:* npm start

wim@ubuntu:~/Blog$ cd Express_Todo_Mongo_API && npm install
cookie-parser@1.3.5 node_modules/cookie-parser
├── cookie@0.1.3
└── cookie-signature@1.0.6

debug@2.2.0 node_modules/debug
└── ms@0.7.1

serve-favicon@2.2.1 node_modules/serve-favicon
├── fresh@0.2.4
├── ms@0.7.1
├── parseurl@1.3.0
└── etag@1.6.0 (crc@3.2.1)

morgan@1.5.3 node_modules/morgan
├── basic-auth@1.0.3
├── depd@1.0.1
└── on-finished@2.2.1 (ee-first@1.1.0)

body-parser@1.12.4 node_modules/body-parser
├── content-type@1.0.1
├── bytes@1.0.0
├── depd@1.0.1
├── raw-body@2.0.2 (bytes@2.1.0)
├── qs@2.4.2
├── on-finished@2.2.1 (ee-first@1.1.0)
├── type-is@1.6.4 (media-typer@0.3.0, mime-types@2.1.2)
└── iconv-lite@0.4.8

express@4.12.4 node_modules/express
├── merge-descriptors@1.0.0
├── utils-merge@1.0.0
├── cookie-signature@1.0.6
├── methods@1.1.1
├── fresh@0.2.4
├── cookie@0.1.2
├── escape-html@1.0.1
├── range-parser@1.0.2
├── content-type@1.0.1
├── finalhandler@0.3.6
├── vary@1.0.0
├── parseurl@1.3.0
├── serve-static@1.9.3
├── content-disposition@0.5.0
├── path-to-regexp@0.1.3
├── depd@1.0.1
├── on-finished@2.2.1 (ee-first@1.1.0)
├── qs@2.4.2
├── etag@1.6.0 (crc@3.2.1)
├── proxy-addr@1.0.8 (forwarded@0.1.0, ipaddr.js@1.0.1)
├── send@0.12.3 (destroy@1.0.3, ms@0.7.1, mime@1.3.4)
├── type-is@1.6.4 (media-typer@0.3.0, mime-types@2.1.2)
└── accepts@1.2.10 (negotiator@0.5.3, mime-types@2.1.2)

jade@1.9.2 node_modules/jade
├── character-parser@1.2.1
├── void-elements@2.0.1
├── commander@2.6.0
├── mkdirp@0.5.1 (minimist@0.0.8)
├── with@4.0.3 (acorn-globals@1.0.4, acorn@1.2.2)
├── constantinople@3.0.1 (acorn-globals@1.0.4)
└── transformers@2.1.0 (css@1.0.8, promise@2.0.0, uglify-js@2.2.5)

We can then start the node app generated by the Express generator:

wim@ubuntu:~/Blog/Express_Todo_Mongo_API$ node bin/www

Going to http://localhost:3000 will show you a welcome message from the Express installer. Perfect, we have generated an Express application upon which we can continue to build further.

Let’s continue with the database section. As the database we will use is Mongo, we will also need to install a package to be able to work with Mongo. My preference goes to Mongoose, which is an object modeling tool which allows us to easily interact with Mongo from our Express code.. Let’s install the mongoose package using ‘npm install mongoose’.

Let’s now go ahead and change some code. You’ll see that the boilerplate code is referring to a users route file. You can see this by looking at the app.js file where it mentions ‘var users = require(‘./routes/users’);’. As we’d rather use todo items instead of users, we will make some changes:

We will add some references in the app.js file to ensure our Express app uses the Mongoose model and the correct Mongo database:

var routes = require('./routes/index');
var users = require('./routes/users');

app.use('/', routes);
app.use('/users', users);

to

var index = require('./routes/index');
var todo = require('./routes/todo');

app.use('/', index);
app.use('/todos', todo);

We will rename the users.js route file in the ‘routes’ folder to ‘todos.js’. In the app.js file we will need to add a variable todos that refers to the ‘./routes/todos’ file. Just to be on the safe side, let’s ensure that the app is still working by launching it:

wim@ubuntu:~/Blog/Express_Todo_Mongo_API$ node bin/www
GET / 304 786.113 ms - -
GET /stylesheets/style.css 200 30.333 ms - 110

OK, so everything still works. Good! Let’s continue…

We will now add the Mongo/Mongoose references to app.js.

var mongoose = require('mongoose'); 
var database = require('./config/database'); 
mongoose.connect(database.url); 

In the database.js file under config we just place the URL to our database. The contents of the database.js file is

module.exports = {
	url : 'mongodb://localhost/todo'
}

OK, we are now ready with the basic changes to the boilerplate code. Let’s know focus on adding the functionality to our todo app.

The first thing to do is to define the Schema that we will be using. Our app basically needs to be told what the fields are to be used in the Mongo database. To achieve that, create a folder called ‘models’ in the root of your application and make a file called ‘todo.js’ in that folder.

var mongoose = require('mongoose');

// todo model
var todoSchema = new mongoose.Schema({
    content: String,
    description:String,
    completed: { type: Boolean, default: false },
    created_at: { type: Date, default: Date.now },
    updated_at: { type: Date, default: Date.now }
});

module.exports = mongoose.model('Todo', todoSchema);

In the above snippet, you can see that we want to store the content, description to the Mongo database, but also want to be able to indicate if a todo item has been completed or not.

Next, we will add the routes to the ‘routes/todo.js’ file. We will explain the example to get the list of all todo items. Note: don’t forget to refer to the database model from the route file. In other words, ensure you are putting the following line at the top of the ‘route/todo.js file’:

var Todo = require('../models/todo');

Here is the code to define a route to retrieve all todo items:

router.route('/')
    //GET all todos
    .get(function(req, res, next) {
        Todo.find({}, function (err, todos) {
              if (err) {
                  return console.error(err);
              } else {
              		console.log("Showing all todo items");
              		res.format({
                    html: function(){
                        res.render('todos/index', {
                            title: 'All my todos', 
                            "todos" : todos
                        });
                    },
                    json: function(){
                        res.json(todos);
                    }
                });
              }     
        });
    })

Here you can see that we define an HTTP GET function that will retrieve all todo items from the Mongo database. We also return an HTML section and a JSON section. Strictly speaking, given the fact we are only developing the REST API server, we don’t need to return the HTML section. Having said that, we’ll leave it in for future use.

In the previous snippet, we have only shown the ‘easy’ part, which was listing all todo items from the database. To avoid that this post is going to be extremely long, I have added -just for informational purposes- the entire content of the todo.js file:

var express = require('express');
var router = express.Router();
var Todo = require('../models/todo');

// GET New Todo page
router.get('/new', function(req, res) {
	console.log("Show page to create new todo item");
    res.render('todos/new', { title: 'Add New Todo' });
});

// route middleware to validate :id
router.param('id', function(req, res, next, id) {
    Todo.findById(id, function (err, todo) {
        //if it isn't found, we are going to repond with 404
        if (err) {
            console.log("Todo item with " + id + " was not found in the database");
            res.status(404)
            var err = new Error('Not Found');
            err.status = 404;
            res.format({
                html: function(){
                    next(err);
                },
                json: function(){
                    res.json({message : err.status  + " " + err});
                }
            });
        //if it is found we continue on
        } else {
            console.log(todo);
            req.id = id;
            next(); 
        } 
    });
});

// -----------------------------------------------------------------------------------------
// Matches routes without identifiers

router.route('/')
    //GET all todos
    .get(function(req, res, next) {
        Todo.find({}, function (err, todos) {
              if (err) {
                  return console.error(err);
              } else {
              		console.log("Showing all todo items");
              		res.format({
                    html: function(){
                        res.render('todos/index', {
                            title: 'All my todos', 
                            "todos" : todos
                        });
                    },
                    json: function(){
                        res.json(todos);
                    }
                });
              }     
        });
    })

	//POST a new todo item
    .post(function(req, res) {
		var content = req.body.content;
        var completed = false;
        var description = req.body.description;


        Todo.create({
            content : content,
            completed : completed,
            description : description,
           
        }, function (err, todo) {
            if (err) {
            	res.send("Todo item was not created succesfully");
                console.log("Todo item was not created succesfully");
            } 
            else {
                console.log('Created new todo item: ' + todo);
                res.format({
					html: function(){
                        res.location("todos");
                        res.redirect("/todos");
                    },
                    json: function(){
                        res.json(todo);
                    }
                });
              }
        })
    });

// -----------------------------------------------------------------------------------------
// Matches routes with identifiers

/* SHOW single todo item */
router.route('/:id/show')
	.get(function(req, res) {
    	Todo.findById(req.id, function (err, todo) {
	      	if (err) {
	        	console.log('Todo item with id ' + todo._id + ' could not be found ' + err);
	        	res.send('Todo item with id ' + todo._id + ' could not be found ' + err);
	      	} 
	      	else {
	        	console.log('Show todo item with id ' + todo._id);
	        	res.format({
		        	html: function(){
		             	res.render('todos/show', {
		                	"todo" : todo
		              	});
		          	},
		          	json: function(){
		              	res.json(todo);
		          	}
	        	});
	      	}
    	});
  	});

/* EDIT single todo item */
router.route('/:id/')
	//GET single todo item
	.get(function(req, res) {
	    Todo.findById(req.id, function (err, todo) {
	        if (err) {
	            console.log('Todo item could not be found ' + err);
	        } 
	        else {
	        	console.log('Edit todo item with id ' + todo._id);
              	res.format({
	                html: function(){
	                    res.render('todos/edit', {
	                        title: 'Todo' + todo._id,
                            "todo" : todo
	                    });
	                },
	                json: function(){
	                    res.json(todo);
	                }
	            });
	        }
	    });
	})

	//PUT to update todo item
	.put(function(req, res) {
	    var content = req.body.content;
	    var description = req.body.description;
	    var completed = req.body.completed;
	    var updated_at = new Date();
	    console.log("New time is " + updated_at);
	   
	    Todo.findById(req.id, function (err, todo) {
	        todo.update({
	            content : content,
	            description : description,
	            completed : completed,
	            updated_at : updated_at,
	           
	        }, function (err, todoID) {
	        	if (err) {
	        		console.log('Todo item could not be found ' + err);
	            	res.send('Todo item could not be found ' + err);
	          	} 
	          	else {
	          		console.log('Updated todo item with id ' + todo._id);
	                res.format({
	                    html: function(){
	                        res.redirect("/todos");
	                    },
	                    //JSON responds showing the updated values
	                    json: function(){
	                        res.json(todo);
	                    }
	                });
	           	}
	        })
	    });
	})

	//DELETE a todo item
	.delete(function (req, res){
	    Todo.findById(req.id, function (err, todo) {
	        if (err) {
	            console.log('Todo item could not be found ' + err);
	            res.send('Todo item could not be found ' + err);
	        } 
	        else {
	            todo.remove(function (err, todo) {
	                if (err) {
	                    console.log('Todo item could not be deleted ' + err);
	            		res.send('Todo item could not be deleted ' + err);
	                } 
	                else {
	                    console.log('Deleted todo item with id ' + todo._id);
	                    res.format({
	                        html: function(){
	                            res.redirect("/todos");
	                        },
	                        //JSON returns the item with the message that is has been deleted
	                        json: function(){
	                            res.json({message : 'deleted',
	                                item : todo
	                            });
	                        }
	                    });
	                }
	            });
	        }
	    });
	});


module.exports = router;

We have now created a complete CRUD application that exposes itself as a REST Server that will return Json objects. We are now completely ready for testing the API.

First off, we want to show all items currently in the Mongo database. Obviously, this should be empty as we’ve been developing from scratch:
GetAllItems_empty

Let’s add a todo item to the database:

2.Post_Todo_item

Let’s then be really sure it was added properly:

3.RetrieveAllItems

You will see that the todo item was properly added to the database. In order to only view that single item, refer to the following screenshot:

5.ShowItem

Let’s also update the item now:

4.UpdateItem

And finally also delete it:
6.DeleteItem

Let’s ensure the item was properly deleted:
8.getitems

Adding the source code to Github

Let’s now add everything to Github. First create a new repository on Github. I typically do this through the webinterface. Then, continue to add the source code to the repository using following steps.

wim@ubuntu:~/Blog/Express_Todo_Mongo_API$ git init
wim@ubuntu:~/Blog/Express_Todo_Mongo_API$ echo "# Express_Todo_Mongo_API" >> README.md
wim@ubuntu:~/Blog/Express_Todo_Mongo_API$ git add .
wim@ubuntu:~/Blog/Express_Todo_Mongo_API$ git commit -m "First Commit"
wim@ubuntu:~/Blog/Express_Todo_Mongo_API$ git remote add origin https://github.com/wiwa1978/blog-express_todo_mongo_api.git
wim@ubuntu:~/Dropbox/Programming/Blog/Express_Todo_Mongo_API$ git push -u origin master

Please refer to the link here to see the final result.