Coding Notes

Building a Node.js REST API 5

Introduction

Featured

Node.js RESTful Express Routing

Building a Node.js REST API 5

Posted by Mauricio on .

Introduction

Welcome again! If you reached this post from nowhere and don’t understand what all this is about, you can check out the previous posts:

You can find the code for this tutorial in GitHub.

If you followed the steps from the previous posts your project directory structure should look like this:

├── config
│   ├── env
│   │   ├── development.js
│   │   └── index.js
│   └── express.js
├── gulpfile.babel.js
├── index.js
├── package.json
└── server
    └── models
        ├── task.js
        └── user.js

If it doesn’t, please go back and check if you missed something from the previous posts.

Creating the Server Routes

Now it’s time to play a bit with our express server and start adding some functionality (interesting functionality) to it. Our first job is to organize the server code, so for that purpose we’ll create two new directories under the server directory:

cd server
mkdir routes
mkdir controllers

Basically in the routes directory we’ll put all the route configuration related code, and in the controllers directory we’ll put all the implementations for the routes.

Before we start creating the routes let’s understand a bit how a REST API should be organized in terms of endpoints.

RESTful Endpoints

RESTful APIs basically are the entry point to retrieve and modify “Resources”. A resource might be any entity that lives in your system context, for example users and tasks will be the resources of our API. So with our REST API we’ll provide clients with a way to retrieve, create, modify and delete resources (users and tasks). But how do we organize the endpoints to do all those kinds of operations?

REST defines a clear and simple way to define the API endpoints which relies on the HTTP methods or verbs: GET, POST, PUT and DELETE:

  • GET: will be used to retrieve resources data
  • POST: will be used to create new resources
  • PUT: will be used to update existing resources
  • DELETE: yeah, as you imagined, will be used to delete resources

REST goes also a step further and tells us how our endpoint URLs should look like. So let’s say we have a resource called user, which represents all the users in our system. We’ll need to create at least five different endpoints for the user resource (and any other resource):

  • GET /users: will return the list of users in our system
  • POST /users: will create a new user in our system
  • GET /users/[userId]: will return the user with the given userId
  • PUT /users/[userId]: will update the data for the user with the given userId
  • DELETE /users/[userId]: will delete the user with the given userId

You can also create other endpoints as listed here but it’s not very common, and we’re not going to create them for the purpose of this tutorial. So we’ll follow this same approach and create those endpoints for our users and tasks resources.

Route Configuration

Let’s start by creating an index.js file inside the routes folder. Inside this file we’ll put the “health check” endpoint we had defined in our config/express.js file and we’ll mount the rest of the endpoints later. So for now the server/routes/index.js file will look like this:

import express from 'express';

const router = express.Router();

/** GET /api-status - Check service status **/
router.get('/api-status', (req, res) =>
  res.json({
    status: "ok"
  })
);

export default router;

Here we define an express router (more about express routing here). And in this router we create a get endpoint on the /api-status path, which will answer with a JSON object saying that the API is ok when it’s running.

Now that we’ll have our routes configured separately in the routes directory we can mount them in our server. To do that open the config/express.js file and do the following changes:

  1. Remove the app.get('/')... since we now have our routes defined separately
  2. Import the new routes configuration
  3. Mount the routes in the /api path of the app.

After this 3 changes the express.js file will look like this:

import express from 'express';
import routes from '../server/routes';

const app = express();

// mount all routes on /api path
app.use('/api', routes);

export default app;

Now if you run gulp nodemon and call the api-status endpoint we defined, you should get an answer:

$ curl http://localhost:3000/api/api-status
{"status":"ok"}

User Routes

Before we define our user routes, we need to add a middleware to our express app that will be very helpful called body parser. As the body parser docs state it allows you to:

Parse incoming request bodies in a middleware before your handlers, available under the req.body property.

So basically whenever a PUT or POST request arrives with data in the request body, it will parse it and put it as a json object in the req.body field so that we can use that data in our route handlers. Let’s install it!

npm install --save body-parser@^1.14.2

And to configure it, add three more lines to the config/express.js file, so it will look like this:

import express from 'express';
import bodyParser from 'body-parser';
import routes from '../server/routes';

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// mount all routes on /api path
app.use('/api', routes);

export default app;

Here we configure two parsers, one for json request bodies (where Content-Type is application/json) and one for urlencoded (where Content-Type is x-ww-form-urlencoded).

The Users Route Controller

All set now, we’re ready to define our first set of RESTful endpoints to manage users. Let’s create a users.js file within the server/controllers directory and put the following contents on it:

import User from '../models/user';

function load(req, res, next, id) {
  User.findById(id)
    .exec()
    .then((user) => {
      req.dbUser = user;
      return next();
    }, (e) => next(e));
}

function get(req, res) {
  return res.json(req.dbUser);
}

function create(req, res, next) {
  User.create({
      username: req.body.username,
      password: req.body.password
    })
    .then((savedUser) => {
      return res.json(savedUser);
    }, (e) => next(e));
}

function update(req, res, next) {
  const user = req.dbUser;
  Object.assign(user, req.body);

  user.save()
    .then((savedUser) => res.sendStatus(204),
      (e) => next(e));
}

function list(req, res, next) {
  const { limit = 50, skip = 0 } = req.query;
  User.find()
    .skip(skip)
    .limit(limit)
    .exec()
    .then((users) => res.json(users),
      (e) => next(e));
}

function remove(req, res, next) {
  const user = req.dbUser;
  user.remove()
    .then(() => res.sendStatus(204),
      (e) => next(e));
}

export default { load, get, create, update, list, remove };

This will be our users controller module, and it contains all the necessary code to implement the five endpoints I’ve described before in this same post. Let’s briefly explain what each of the functions in this controller does:

  • load: we’ll use this controller function for all the requests that contain a userId on the path. This would be for the GET /users/userId, PUT /users/userId and DELETE /users/userId. This will make things easier by loading the user from the database and making it accessible in the request object as req.dbUser.
  • get: implementation for the GET /users/userId endpoint that returns a specific user’s data as json.
  • create: implementation for the POST /users endpoint. It creates a new user document in our database by using the request body data sent by the client.
  • update: implementation for the PUT /users/userId endpoint. It also takes the data sent in the request body and uses it to update an existing user. It returns 204 No Content as recommended in the RFC7231 standard
  • list: implements the GET /users endpoint. It queries the database to list all the users but limits the results. Since we might have thousands of users in our database, we don’t want to send a huge payload through the network, so we need to paginate the results in a way that API clients can get them by batches (of 50 users in our case).
  • remove: implements the DELETE /users/userId endpoint and returns 204 No Content when it’s successful.

Notice that in case of errors happening while accessing the data, we simply call the next middleware with the corresponding error as parameter (next(e)). We’ll cover error handling later, so for now just ignore that part.

The Users Route Configuration

Now that we have a controller ready, we can create our route configuration file. So let’s create a users.js file inside the server/routes directory and put the following contents on it:

import express from 'express';
import userCtrl from '../controllers/users';

const router = express.Router();

router.route('/')
  /** GET /api/users - Get list of users */
  .get(userCtrl.list)

  /** POST /api/users - Create new user */
  .post(userCtrl.create);

router.route('/:userId')
  /** GET /api/users/:userId - Get user */
  .get(userCtrl.get)

  /** PUT /api/users/:userId - Update user */
  .put(userCtrl.update)

  /** DELETE /api/users/:userId - Delete user */
  .delete(userCtrl.remove);

/** Load user when API with userId route parameter is hit */
router.param('userId', userCtrl.load);

export default router;

This one looks simple, so here we’re defining two routes: / and /:userId. We’ll mount this two routes under the /users path later, so they will become /users and /users/:userId. Under each of this routes, we define the different verbs supported on that route, and we assign a controller function to each of them. So as an example, let’s take the first one:

router.route('/').get(userCtrl.list)

This is telling express, that whenever a request comes to the /users path with the HTTP method GET, the controller function list must be called to handle the request. You can guess the rest of the routes very easily.

There’s one special configuration in the end:

router.param('userId', userCtrl.load)

This tells express that whenever an incoming request has the userId parameter in the path, it should call first the controller function load and then pass to the corresponding handler. So if for example the server gets the request: GET /users/some-user-id-here, the server will first call the userCtrl.load function and after that it will call the userCtrl.get function.

Finally we have to mount the routes into our express app so that they will be accessible (special thanks to Dave Harned for pointing out I missed this part!). For this purpose we’ll go to the server/routes/index.js file and add the following lines:

import userRoutes from './users';

...

router.use('/users', userRoutes);

We’re first importing the user routes definitions that we did just made and then it’s mounting these definitions into the /users path. So whenever a requests comes to the server starting with /users, express will check for route definitions inside our userRoutes. So whatever we defined within userRoutes will be called when the URL path starts with /users. Let’s try to clarify that with an example: if the server gets a request like this GET /users/123456, then the GET /:userId handler defined in userRoutes will be called.

Testing the Endpoints

All set, let’s grab a console and try our set of endpoints. Let’s start by creating a new user with our POST /users endpoint:

# POST /users
$ curl -X POST http://localhost:3000/api/users \
    -d username=test_user \
    -d password=hello_world

And the response should be something like this:

{
    "__v":0,
    "username":"test_user",
    "password":"$2a$10$stMU9L4tiQBVTz1ng1Yi0uqvIrHAL...",
    "_id":"579949227038c8e0b2e399a7"
}

And if we request a list of users with our GET /users endpoint, the response will be similar but wrapped in an array:

# GET /users
$ curl http://localhost:3000/api/users
[
  {
    "__v":0,
    "username":"test_user",
    "password":"$2a$10$stMU9L4tiQBVTz1ng1Yi0uqvIrHAL...",
    "_id":"579949227038c8e0b2e399a7"
  }
]

We have a new user with _id value 579949227038c8e0b2e399a7. So let’s use that id to try out other endpoints:

# GET /users/:userId
$ curl http://localhost:3000/api/users/579949227038c8e0b2e399a7
{
    "__v":0,
    "username":"test_user",
    "password":"$2a$10$stMU9L4tiQBVTz1ng1Yi0uqvIrHAL...",
    "_id":"579949227038c8e0b2e399a7"
}

Now let’s update the username property of our only user:

# PUT /users/:userId
$ curl -X PUT http://localhost:3000/api/users/579949227038c8e0b2e399a7 \
    -d username=test_user_changed

In this case the response is just the status 204 No Content, so we won’t get anything back

Finally let’s remove the user with the DELETE /users/:userId endpoint:

# DELETE /users/:userId
$ curl -X DELETE http://localhost:3000/api/users/579949227038c8e0b2e399a7

And again, no answer for the DELETE operations.

Task Routes

The routes configuration and implementation for tasks will be very similar, so I’ll just put here the contents for the routes/tasks.js and controllers/tasks.js files, if something’s not clear feel free to ask in the comments!

The Task Routes Controller

Create a tasks.js file inside the server/controllers directory and put the following contents on it:

import Task from '../models/task';

function load(req, res, next, id) {
  Task.findById(id)
    .exec()
    .then((task) => {
      req.dbTask = task;
      return next();
    }, (e) => next(e));
}

function get(req, res) {
  return res.json(req.dbTask);
}

function create(req, res, next) {
  Task.create({
      user: req.body.user,
      description: req.body.description
    })
    .then((savedTask) => {
      return res.json(savedTask);
    }, (e) => next(e));
}

function update(req, res, next) {
  const task = req.dbTask;
  Object.assign(task, req.body);

  task.save()
    .then(() => res.sendStatus(204),
      (e) => next(e));
}

function list(req, res, next) {
  const { limit = 50, skip = 0 } = req.query;
  Task.find()
    .skip(skip)
    .limit(limit)
    .exec()
    .then((tasks) => res.json(tasks),
      (e) => next(e));
}

function remove(req, res, next) {
  const task = req.dbTask;
  task.remove()
    .then(() => res.sendStatus(204),
      (e) => next(e));
}

export default { load, get, create, update, list, remove };

The Task Routes Configuration

Create a tasks.js file inside the server/routes directory and put the following contents on it:

import express from 'express';
import taskCtrl from '../controllers/tasks';

const router = express.Router();

router.route('/')
  /** GET /api/tasks - Get list of tasks */
  .get(taskCtrl.list)

  /** POST /api/tasks - Create new task */
  .post(taskCtrl.create);

router.route('/:taskId')
  /** GET /api/tasks/:taskId - Get task */
  .get(taskCtrl.get)

  /** PUT /api/tasks/:taskId - Update task */
  .put(taskCtrl.update)

  /** DELETE /api/tasks/:taskId - Delete task */
  .delete(taskCtrl.remove);

/** Load task when API with taskId route parameter is hit */
router.param('taskId', taskCtrl.load);

export default router;

Finally add a two new lines to server/routes/index.js to mount the tasks routes in the /tasks path:

...
import taskRoutes from './tasks';
...
router.use('/tasks', taskRoutes);

Extending the API

You’re free to extend the API with as many endpoints as you want. For example you might want to add an endpoint to retrieve all the tasks for a specific user. Or you can also add some filtering capabilities through query parameters like GET /tasks?done=false to return all the tasks that are not done yet.

Coming up next…

We have our endpoints up and running, now it’s time to add some security to our RESTful API. We’ll see how to add authentication via JSON Web Tokens in the next post

If you enjoyed reading, retweet or like this tweet!

Creative Commons License
Building a Node.js REST API 5: The Routes by Mauricio Payetta is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License .
user

Mauricio

http://blog.mpayetta.com

I'm a fullstack engineer who loves travelling and learning. I co-founded Bithive.io, a fully distributed software consultancy firm. I've been part of the Lending Club, TomTom and Dianrong.com