A simple organization scheme for handling routes in ExpressJS apps

The ExpressJS framework is one of the simpler yet very powerful web frameworks for NodeJS. It provides a simple way to expose GET / POST endpoints on your web application, which then serves the appropriate response. Getting started with ExpressJS is easy and the Guides on the ExpressJS website are very well written to make you effective in short order.

Moving towards a flexible app structure

When you have a simple app with a few endpoints, it is easy to keep everything self-contained right inside of the top-level app.js. However as you start buliding up more GET / POST endpoints, you need to have an organization scheme to help you manage the complexity. As a simple rule,

When things get bigger, they need to be made smaller ;-)

Fortunately, several smart folks have figured this out earlier and have come up with approaches that are wildly successful. Yes, I am talking about Rails and the principle of “Convention over Configuration”. So lets apply them to our constantly growing app.

Route management

Most of the routes (aka restful endpoints) that you expose on your app can be logically grouped together, based on a feature. For example, if you have some endpoints such as:

  • /login
  • /login/signup
  • /login/signup/success
  • /login/lostpassword
  • /login/forgotusername

… you can try grouping them under the “login” feature. Similarly you may have other endpoints dedicated to handle other workflows in your app, like uploading content, creating users, editing content, etc. These kind of routes naturally fit into a group and that’s the first cue for breaking them apart. As a first step, you can put the logically related GET / POST endpoints in their own file, eg: login.js. Since you may have several groups of routes, you will end up with lots of route files.

Putting all of these files at the top-level is definitely going to cause a clutter. So to simplify this further, put all of these files into a sub-folder, eg: /routes. The project structure now looks more clean:

1 	|- routes
2 	|	|- login.js
3 	|	|- create_users.js
4 	|	|- upload.js
5 	|	|- edit_users.js
6 	|- app.js

Since we are working with NodeJS, each file becomes a module and the objects in the module can be exposed via the exports object. We can establish a simple protocol that each route module must have an init function which we call from app.js, passing in the necessary context for the route. In case of the login this could look like so:

 1 function init(app) {
 3 	app.get('/login', function (req, res){ 
 5 	});
 7 	app.get('/login/signup', function (req, res){ 
 9 	});
11 	app.get('/login/signup/success', function (req, res){ 
13 	});
15 	app.get('/login/lostpassword', function (req, res){ 
17 	});
19 	app.get('/login/forgotusername', function (req, res){ 
21 	});
23 }

If you are using a recent version of ExpressJS, 2.5.8 as of this writing, the command-line interface provides a way to quickly generate the express app. If you type express [options] name-of-the-app, it will generate a folder named name-of-the-app in the current working directory. Not surprisingly, express creates the /routes folder for you, which is already taking you in the right direction. I only learnt this recently and have so far been doing the hard work of starting from scratch each time. Sometimes spending a little more time on the manual helps! RTFM FTW.

Once we have the route files as described, it is easy to load them from app.js. Using the filesystem module we can quickly load each module and call init() on each one of them. We do this before the app is started. The app.js skeleton looks like so:

 1 var fs = require('fs'),
 2     path = require('path');
 4 var RouteDir = 'routes',
 5     files = fs.readdirSync(RouteDir);
 7 files.forEach(function (file) {
 8     var filePath = path.resolve('./', RouteDir, file),
 9         route = require(filePath);
10     route.init(app);
11 });

Now we can just keep adding more routes, grouped in their own file and continue to build several endpoints without severerly complicating the app.js. The app.js file now follows the Open-Closed-Principle (app.js is open for extension but closed for modification).

In short…

As you can see, it is actually a simple idea, but when applied to other parts of your application, it can substantially reduce the maintenance overhead. So in summary:

  • Establish conventions to standardize a certain aspect of the program. In our case it was routes.
  • Group related items into their own module
  • Collect the modules into a logical folder and load from that folder