Hello and welcome to part 4 of my tutorial Building a REST API with MongoDB, Node and Express. If you haven't already please check out part 3. In this part we will implement our posts API. As the code will largely be rather similar I will provide the complete files and illustrate key differences.

As always the code is available from this repository: http://gogs.dev.dylanscott.me/dylanrhysscott/series-rest-api

Posts Model

Following our convention we create the file models/post.js

const mongoose = require('mongoose');  
const Schema = mongoose.Schema;  
const PostSchema = new Schema({  
    postTitle: {
        type: String,
        required: true
    },
    postBody: {
        type: String,
        required: true
    },
    _user: {
        type: Schema.Types.ObjectId,
        required: true,
        ref: 'users'
    }
});
const PostModel = mongoose.model('posts', PostSchema);  
module.exports = {  
  createPost: (e, post) => {
      const promise = new Promise((resolve, reject) => {
        var postInstance = new PostModel(post);
        postInstance.save((err, result) => {
          if(err) {
            reject(err);
          }

          if(result) {
            resolve(result);
          }
        });
      });

      promise.then((result) => {
        e.emit('createPost', null, result);
      })
      .catch((err) => {
        e.emit('createPost', err, null);
      });
  },

  getPosts: (e) => {
      const promise = new Promise((resolve, reject) => {
        PostModel.find({}).populate('_user').exec((err, results) => {
          if(err) {
            reject(err);
          }

          if(results) {
            resolve(results);
          }
        });
      });

      promise.then((posts) => {
        e.emit('getPosts', null, posts);
      })
      .catch((err) => {
        e.emit('getPosts', err, null);
      });
    },

  getPost: (e, postId) => {
      const promise = new Promise((resolve, reject) => {
        PostModel.find({_id: postId}).populate('_user').exec((err, result) => {
          if(err) {
            reject(err);
          }

          if(result) {
            resolve(result);
          }
        });
      });

      promise.then((post) => {
        e.emit('getPost', null, post);
      })
      .catch((err) => {
        e.emit('getPost', err, null);
      });
  },

  updatePost: (e, postId, post) => {
      const promise = new Promise((resolve, reject) => {
        PostModel.update({_id: postId}, {$set: post}, (err, result) => {
          if(err) {
            reject(err);
          }

          if(result) {
            resolve(result);
          }
        });
      });

      promise.then((post) => {
        e.emit('updatePost', null, post);
      })
      .catch((err) => {
        e.emit('updatePost', err, null);
      });
  },

  deletePost: (e, postId) => {
      const promise = new Promise((resolve, reject) => {
        PostModel.remove({_id: postId}, (err, result) => {
          if(err) {
            reject(err);
          }

          if(result) {
            resolve(result);
          }
        });
      });

      promise.then((post) => {
        e.emit('deletePost', null, post);
      })
      .catch((err) => {
        e.emit('deletePost', err, null);
      });
  }
}

There are several things you may notice that are new about this model:

  • In the schema we are using Schema.Types.ObjectId as the type. This allows us to define a Mongoose ID as a field. You'll recall we want to associate posts with users. This is one way in MongoDB we can define a relationship
  • Also we are using the ref property. This allows us to tell Mongoose which model our ID refers to ('users').
  • The _users field in our schema is a convention with Mongoose. Related fields are prefixed with an _.
  • For our retrieves getUser() and getUsers() rather than seeing just an ID we would like to see user information. To do this rather than passing a callback directly to the find methods we have chained a .populate() method with the field we want to populate. This instructs Mongoose to query our users collection and replace the ID with our actual data. Once the populate has been chained we execute the query.

Post Routes

Next we create our routes file routes/posts.js for this section of our API:

var express = require('express');  
var router = express.Router();  
var EventEmitter = require('events');  
var PostModel = require('../models/post.js');

router.post('/', (req, res, next) => {  
  var PostEvents = new EventEmitter();
  var post = req.body;
  PostEvents.once('createPost', (err, post) => {
    if(err) {
      res.status(500).json({message: 'Could not create post', err: err});
    }

    if(post) {
      res.status(200).json(post);
    }
  });
  PostModel.createPost(PostEvents, post);
});

router.get('/', (req, res, next) => {  
  var PostEvents = new EventEmitter();
  PostEvents.once('getPosts', (err, posts) => {
    if(err) {
      res.status(500).json({message: 'Could not retrieve posts', err: err});
    }

    if(posts) {
      res.status(200).json(posts);
    }
  });

  PostModel.getPosts(PostEvents);
});

router.get('/:id', (req, res, next) => {  
  var PostEvents = new EventEmitter();
  var id = req.params.id;
  PostEvents.once('getPost', (err, post) => {
    if(err) {
      res.status(500).json({message: 'Could not retrieve post', err: err});
    }

    if(post) {
      res.status(200).json(post);
    }
  });

  PostModel.getPost(PostEvents, id);
});

router.put('/:id', (req, res, next) => {  
  var PostEvents = new EventEmitter();
  var id = req.params.id;
  var data = req.body;
  PostEvents.once('updatePost', (err, post) => {
    if(err) {
      res.status(500).json({message: 'Could not update post', err: err});
    }

    if(post) {
      res.status(200).json(post);
    }
  });

  PostModel.updatePost(PostEvents, id, data);
});

router.delete('/:id', (req, res, next) => {  
  var PostEvents = new EventEmitter();
  var id = req.params.id;
  PostEvents.once('deletePost', (err, posts) => {
    if(err) {
      res.status(500).json({message: 'Could not delete post', err: err});
    }

    if(posts) {
      res.status(204).json({});
    }
  });

  PostModel.deletePost(PostEvents, id);
});

module.exports = router;  

Here in the routes we are still following the same RESTful patterns and calling the relevant methods from our new post model and listening for the correct events.

Mounting the new routes

In its current state these routes will not function. When we make requests to our API it will return a 404. Why? Well we haven't told our app to use these routes. It worked with users as the express generator provided that for us. Now we have gone beyond our scaffolded code. To mount the routes we add the following in app.js:

var posts = require('./routes/posts');  
app.use('/posts', posts);  

This tells our app the base point it should listen for our requests. In this case /posts. And that's it! We're ready to test it out...Fire up the app in the usual way.

Testing it out...

Before we start do a GET request to /users and make note of an _id for a user. We will need this for our requests when creating posts. Next run the following tests, changing the ID to suit your environment:

  • Creating a Post

create-post

  • Getting all posts - Notice the population of _user!

get-all-posts

  • Get a single post - Notice the population of _user!

get-single-post

  • Update a post

update-post

  • Delete a post

delete-post

Wrapping up

As you can see once you have laid out the first section of your API its very easy with a few minor tweaks to apply this to different data. We've now fully implemented our API and these techniques can be used to expand it further. Give it a go and see how you get on! Now I don't know about you but manual testing is getting somewhat tedious! In the final part of this series we will take a look at Gulp and a testing framework. These tests will run every time we change our app and make us more productive overall.

Has this tutorial been useful? - Please give me feedback via my twitter handle @dylanrhysscott!