/ Node.js

Building RESTful APIs with Express and MongoDB

WHAT IS REST?

REST or Representational State Transfer is an architectural style — a way to build simple scalable applications for the web. It is based on the client-server architecture and is designed to use a stateless communication protocol, typically HTTP.

REST has the concepts of resource and representation. A resource is basically an object which can be accessed or manipulated using HTTP methods. URIs (Uniform Resource Identifier) are used to identify resources on the network. A representation is a document representing the state of a resource. Although REST has no limitation on how to represent resources, XML and JSON are mostly used.

REST is mainly used to build web services — these services are often called RESTful. A RESTful web service uses HTTP to operate on resources and responds in JSON or XML.

The Application

We're going to build a RESTful API using Express and MongoDB to manage a list of to-do items. We won't build any UI in this post but we will use POSTMAN to test the API.

In summary, the API will:

  • have standard URLs (all endpoints will use nouns instead of verbs and all endpoint names will be plural)
  • use standard HTTP methods (GET, POST, PUT, and DELETE) to handle CRUD operations
  • validate user input
  • respond in JSON

Here's an overview of the API we will create:

Resource URL HTTP verb Description
/api/todos GET Get all tasks
/api/todos/:id GET Get a single task
/api/todos POST Create a new task
/api/todos/:id PUT Update a task
/api/todos/:id DELETE Delete a task

Getting Started

Before going any further, make sure you have Node and MongoDB installed:

brew update
brew install node
brew install mongodb

This is installing Node and MongoDB on a Mac. Other installation options are found on their respective websites.

All set? Let's go...

First thing first: let's create a project directory:

mkdir todo-api

Open the directory and run:

npm init

NPM will ask you a bunch of questions about the project which will result in creating a package.json file:

{
  "name": "todo-api",
  "version": "1.0.0",
  "description": "A RESTful API built with Express to manage a to-do list",
  "keywords": [
    "api",
    "todo",
    "express",
    "mongodb"
  ],
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Djamseed Khodabocus",
  "license": "MIT"
}

The application will rely on some dependencies to work — so, let's install them:

npm install --save express
npm install --save body-parser
npm install --save mongoose

These commands are installing Node modules to our project. If you look in the project directory, you should now see a node_module folder. The --save option tells npm to save the installed dependencies in the package.json file:

// package.json

"dependencies": {
  "body-parser": "^1.15.0",
  "express": "^4.13.4",
  "mongoose": "^4.4.13"
}

So, what did we just installed? Express is a web application framework that runs on Node. body-parser is an express middleware that lets you pull POST content from an HTTP request. Mongoose is an ORM that allows you to interface with the database.

Cool — we have a Node project with all of its dependencies installed. Let's fill out the rest of the project structure:

$ mkdir configs
$ mkdir models
$ mkdir routes
$ touch configs/database.js
$ touch models/task.js
$ touch routes/task.js
$ touch server.js
$ touch README.md
$ touch LICENSE
$ touch .gitignore

This may seem overkill for such a small app but I like to separate things. Everything that can be modeled goes in the models folder and all routes should be defined in the routes folder. The configs folder is used to store application configurations, including environment-specific configs. And as always, include a README file to help other understand your project — same goes for a LICENSE (although it's not necessary).

Setting Up the Server

We now have a project structure to work with — so, let's create a basic server with Express. Node will look for the server.js file to bootstrap the application, so open up this file with your favorite text editor and let's get started:

// server.js

'use strict';

// dependencies
const express = require('express');
const bodyParser = require('body-parser');

// create the express app
const app = express();

// configure the body-parser
// to accept urlencoded bodies
// and json data
app.use(bodyParser.urlencoded({ extended: true }))
   .use(bodyParser.json());

// get an instance of the
// express router
const router = express.Router();

// test route
router.get('/', (req, res) => {
  res.send('API is working');
});

// register all routers
// all routes are prefixed with /api
app.use('/api', router);

// set the port
const port = parseInt(process.env.PORT, 10) || 8000;

// start the server
const server = app.listen(port, () => {
  console.log(`App is running at: localhost:${server.address().port}`);
});

This looks pretty basic. All we've done here was to create an Express app, configured the body-parser middleware, added a route and started the server. The cool thing here is that we are using an instance of the Express router which allows us to create modular and mountable route handlers and use them in the main app — meaning that you can define your routes separately and mount them (like any other Express middleware using app.use(). We haven't done any separation of routes yet (we'll get to that later) — we just need to test our server for now.

To get things up and running, type the following in the console:

node server.js

server started

Now, access the API using the URL localhost:8000/api:

api working

I'm using the curl command line tool here. You can access the URL directly in your browser or in POSTMAN.

"API is working" — Great! The next thing will be to define the model and hook up the database.

Connecting to the Database

First, run the MongoDB service by typing the following in the console:

mongod

Then, open a connection to the database on the running instance of MongoDB (in our case, that will be localhost). So, in configs/database.js, include the following:

// configs/database.js

'use strict';

// dependencies
const mongoose = require('mongoose');

// set the database name
const dbName = 'todo-api';

// connect to the database
mongoose.connect(`mongodb://localhost/${dbName}`);

// get notified if the connection
// was successful or not
const db = mongoose.connection;

db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', () => {
  console.log(`Connected to the ${dbName} database`);
});

Now, let's tell the server how to connect to the database by including the following in server.js:

// server.js

// database connection
require('./configs/database');

Restart the app and you should see something like this:

mongodb-connect

Building the Model

Since we're going to manage to-do items with the API, let's build a Task model. In the models/tasks.js file, add the following code:

// models/task.js

'use strict';

const mongoose = require('mongoose');

const schema = new mongoose.Schema({
  name: { type: String, required: [true, 'A task name is required'] },
  note: { type: String, maxlength: [50, 'Only 50 characters or less are allowed'] },
  completed: { type: Boolean, default: false },
  createdAt: { type: Date, default: Date.now }
});

module.exports = mongoose.model('tasks', schema);

Each Task will have the fields, name, note, completed and createdAt associated with it. Mongoose offers several built-in validators and you can create custom ones as well. I've included some basic validations which will trigger if any input errors are found and provide some nice error messages to the user. Finally, we need to export the model with Node's module.exports so that it can be imported into other modules or scripts.

Building the Routes

The Express router will be used to handle HTTP request coming to the application. There are two approaches for creating routes with the Express router:

  • router.METHOD where METHOD is one of the HTTP methods such as router.get() and router.post()
  • router.route() which allows you to handle multiple HTTP methods having the same URI

We're gonna go with the second approach since a single path can be reused to handle multiple HTTP handlers, thus avoiding duplicate route names.

Since all the routes will be created in a separate module, we'll need to remove the test route (and the express router) from our server and import the router module there. But first, let's define the building block for the router module. Open the routes/task.js file and include the following:

// routes/task.js

'use strict'

const express = require('express');

const router = express.Router();

// route definitions here...

module.exports = router;

Now import the newly created module in server.js. This is how the complete file will look like now:

// server.js

'use strict';

// dependencies
const express = require('express');
const bodyParser = require('body-parser');

// create the express app
const app = express();

// configure the body-parser
// to accept urlencoded bodies
// and json data
app.use(bodyParser.urlencoded({ extended: true }))
   .use(bodyParser.json());

// register all routers
// all routes are prefixed with /api
app.use('/api', require('./routes/task'));

// set the port
const port = parseInt(process.env.PORT, 10) || 8000;

// start the server
const server = app.listen(port, () => {
  console.log(`App is running at: localhost:${server.address().port}`);
});

We're now going to create the routes for getting all tasks and creating a new task which will be both handled using the /api/todos route. At this point, we don't have any data to work with, so we'll implement the route for creating new tasks first.

Creating a New Task

In the "router" module, let's add a new route to handle POST request:

// routes/task.js

'use strict'

const express = require('express');

const Task = require('../models/task');

const router = express.Router();

// routes ending with /todos
router.route('/todos')
  .post((req, res) => {

    const task = new Task({
      name: req.body.name,
      note: req.body.note
    });

    task.save((err) => {
      if (err){
        return res.send(err);
      }

      return res.json({ message: 'New task created!' });
    });

  });

module.exports = router;

Let's test this new route using POSTMAN:

postman - new task

We got back a successful message telling us that the new task has been created. Notice that we are sending the parameters as x-www-form-urlencoded. This will send all of our data to the server as query strings.

In case of any input errors (such as not providing a task name which is a required field), you should get a response error from the server like that:

postman - new task error

Getting All Tasks

Let's go ahead and implement the route to get all tasks:

// routes/task.js

router.route('/todos')
  .post((req, res) => {
   ...
  })
  .get((req, res) => {
    Task.find({}).sort({ createdAt: -1 })
        .exec((err, task) => {
          if (err){
            return res.send(err);
          }
          return res.json(task);
        });
  })

Now, use POSTMAN and send a GET request to /api/todos:

postman - get all tasks

We've handled all routes ending in /todos. We're now going to handle all routes ending with /todos/:id. This single route will be used to:

  • get a single task
  • update a task
  • delete a task

Getting a Single Task

We need to add another router.route() to handle all requests that have a parameter id attached to them:

// routes/task.js

router.route('/todos')
...

// routes starting with /todos/:id
router.route('/todos/:id')
  .get((req, res) => {
    Task.findById(req.params.id, (err, task) => {
      if (err){
        return res.send(err);
      }
      return res.json(task);
    });
  });

Each task has an associated _id which is automatically generated by MongoDB upon insert. Let's grab that id and use it as parameter for the test:

postman - get a single task

Updating a Task

You can easily update a task by sending a PUT request like this:

// routes/task.js

router.route('/todos')
...

// routes starting with /todos/:id
router.route('/todos/:id')
.get((req, res) => {
  ...
  })
 .put((req, res) => {
    Task.findByIdAndUpdate(req.params.id, {
      name: req.body.name,
      note: req.body.note,
      completed: req.body.completed
    }, (err) => {
      if (err){
        return res.send(err);
      }
      return res.json({ message: 'Task updated successfully' });
    });
  })

In this example, I'm marking a particular task as completed:

postman - update task

Deleting a Task

Finally, for deleting a task, just send a DELETE request to the API:

// routes/task.js

// routes starting with /todos/:id
router.route('/todos/:id')
  .get((req, res) => {
   ...
  })
  .put((req, res) => {
    ...
  })
  .delete((req, res) => {
    Task.remove({ _id: req.params.id }, (err) => {
      if (err){
        return res.send(err);
      }
      return res.json({ message: 'Task has been removed!' });
    });
  });

postman - deleting a task

Conclusion

This has been a quick look of creating a RESTful API with Express and MongoDB. The API we've created is very basic and there's much more you can do with it such as authentication or better validation.

You can find the full source code on Github