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.
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 ;-)
-Some wise person
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.
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:
… 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:
|- routes
| |- login.js
| |- create_users.js
| |- upload.js
| |- edit_users.js
|- 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:
function init(app) {
app.get('/login', function(req, res) {});
app.get('/login/signup', function(req, res) {});
app.get('/login/signup/success', function(req, res) {});
app.get('/login/lostpassword', function(req, res) {});
app.get('/login/forgotusername', function(req, res) {});
}
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:
var fs = require('fs'),
path = require('path');
var RouteDir = 'routes',
files = fs.readdirSync(RouteDir);
files.forEach(function(file) {
var filePath = path.resolve('./', RouteDir, file),
route = require(filePath);
route.init(app);
});
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).
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: