Having worked with restify in several projects, I wish there was a way to better organize routes.

With the standard restify setup, you will most likely have a central file where you register your routes and export a server:

// bootstrap.js written in ES6
const restify = require('restify');

function createServer(options) {  
  // assert on options
  const server = restify.createServer({
    name: 'sample-app',
    log: options.log
  });

  // register common parsers on server

  server.get('/hello', function (req, res, next) {
    res.send('Hello World!');
    next();
  });

  server.get('/test', function (req, res, next) {
    res.send('Test!');
    next();
  });

  // ... more routes

  return server;
}

module.exports = createServer;  

If we have a lot of routes to register, we can see how this file will quickly become too large to manage effectively.

A slightly better approach would be to define our handlers in separate modules and refer to them by name in our bootstrap file:

// bootstrap.js
const restify = require('restify');  
const handlers = require('./handlers');

function createServer(options) {  
  // ... initialize server
  server.get('/hello', handlers.hello);
  server.get('/test', handlers.test);
  // more routes
  return server
}
// ...

This approach is better but it still has an issue. Although we managed to separate handler definitions from our main bootstrap file, the path at which we register the handlers is still defined in the bootstrap file.

In restify, if the path you define contains parameters or regex, your handler definition is highly dependent on the path definition. It would be ideal if we could package the path definition and the handler definition together in the same file as a single modular unit.

To accomplish this, instead of exporting handlers from our modules, we need to export some object that contains all the necessary information to register a route in restify:

  • Path
  • Name (optional)
  • Version (optional)
  • Handlers

Lets call this object a Router.

The router needs to meet the following basic criteria:

  1. Easy to use
  2. Encapsulate all route level details (path, method, version, handlers)
  3. Have a way to apply routes to a restify server instance

In order to make this component meet requirements 1 and 2, we can re-use the interface for registering routes on the restify server.

Example interface:

router.get('/hello', handler)

router.post('/test', handler)

Re-using the same interface allows users of restify to easily adopt this component without needing to learn a new pattern.

In order to meet requirement 3, we need a method on the router which registers the defined routes on a given restify server instance:

router.applyRoutes(server)

Calling the applyRoutes method would take care of registering routes on a restify server instance.

By grouping our routes into multiple Router modules and applying them to a server, we can achieve a good level of modularity with our route definitions.

I've created a simple component which implements this pattern, available as restify-router on NPM.

Restify Router

Lets walk through an example usage of it.

For our example we have two sets of routes in our application:

Users:

  • GET /users
  • GET /users/:id

Posts:

  • GET /posts
  • GET /posts/:id

First we need to install the component:

Installation

$ npm install --save restify-router

Creating a routers for our routes

To create our routes for users we can create a file users.js which defines our routes as we would on a restify server instance.

// users.js
const Router = require('restify-router').Router;  
const router = new Router();

function all(req, res, next) {  
  const users = getAllUsers(); // fetch all users
  res.send(users);
  next();
}

function byId(req, res, next) {  
 const user = getUserById(req.params.id); // fetch single user
 res.send(user);
 next();
}

// add a routes like you would on a restify server instance
router.get('/users', all);  
router.get('/users/:id', byId);

module.exports = router;  

Similarly we can create a file called posts.js which defines routes in a similar fashion for posts.

Once we have our Routers defined we can require these modules in a bootstrap file:

// bootstrap.js
const userRouter = require('./users');  
const postsRouter = require('./posts');  
const restify = require('restify');

function createServer(options) {

  const server = restify.createServer({ ... });

  // add user routes
  userRouter.applyRoutes(server);

  // add posts routes
  postsRouter.applyRoutes(server);

  return server;
}

module.exports = createServer;  

As we can see, Routers act similar to the restify server except they are only containers for route definitions. This pattern allows us to group and modularize routes more effectively in applications with many routes. Routes can be passed around as a single modular unit.

Prefixing Routes

Now that we can group routes as a single unit, it would be nice if we could namespace the group of routes with a path prefix.

This would allow us to specify a prefix for all routes registered on a Router at the time of application.

To prefix all routes, specify the prefix as the second argument to router.applyRoutes(server, prefix)

Example:

Routes:

  • GET /admin/settings
  • GET /admin/controls
const Router = require('restify-router').Router;  
const restify = require('restify');

function settings(req, res, next) {  
  res.send('settings');
  next();
}

function controls(req, res, next) {  
  res.send('controls');
  next();
}

const routerInstance = new Router();

// add a route like you would on a restify server instance
routerInstance.get('/settings', settings);  
routerInstance.get('/controls', controls);

const server = restify.createServer();  
// add all routes registered in the router to this server instance with uri prefix 'admin'
routerInstance.applyRoutes(server, '/admin');

server.listen(8080, function() {  
  console.log('%s listening at %s', server.name, server.url);
});

As we can see, prefixing our routes becomes quite easy since the router acts as a singular unit.

Common Middleware

There may be times when you want to apply some common middleware to all routes registered with a router.
For example, you may want some common authorization middleware for all routes under a specific router.

To stay consistent with the restify server interface, the method on the Router is:

  • .use(middlewareFn, middlewareFn2, ...)
  • .use([middlewareFn, middlewareFn2, ...])

All middleware registered via .use will be applied before route level middleware.

Note: Multiple calls to .use will result in aggregation of middleware, each successive call will append to the list of common middleware

Example Usage

For the sake of simplicity, lets say we want to add a common middleware to all routes for our router. This middleware will check if the user passed a role=admin query parameter. If this parameter is present, the middleware will call the next middleware in the chain, otherwise it will return an Unauthorized status code.

// include restify errors

var router = new Router();

// this will run before every route on this router
router.use(function (req, res, next) {  
   if (req.query.role === 'admin') {
    return next();
   } else {
    return next(new errors.UnauthorizedError());
   }
});

router.get('/hello', function (req, res, next) {  
   res.send('Hello');
   next();
});

router.get('/test', function (req, res, next) {  
   res.send('Test');
   next();
});

router.applyRoutes(server);

// calling GET /hello  runs use middle ware first and then the routes middleware

In the above example, we define our common middleware via the .use method of the router. The middleware registered with this method will execute before each route handler.

Thoughts

Creating a router component is fairly trivial but it can significantly improve the way in which we organize route definitions with restify.

Let me know what you think and if you have any suggestions feel free to create an issue on Github or issue a PR.