Friday, September 12, 2014

Express Routers as Middleware

Lately I've been developing my web applications in JavaScript, using Node with the Express framework. It's easy to get something thrown together and working very quickly, but like anything else, if you want to maintain it for long it should be well-organized.

Using Routers as Middleware is nice way of partitioning your applications into smaller, easier-to-handle modules that can even be re-used in other projects.

Sometimes you end up with a lot of different routes that have similar prefixes. In my comic application I have things such as

GET  /images
GET  /images/<image>
POST /images
GET  /data/<id>
PUT  /data/<id>
POST /data


...mixed in with a bunch of other routes. All of the /data/* routes are related, and all of the /images/* routes are related. It would be nice to extract them out into their own modules.

We can do this by creating Routers for each set of related paths and using them as Middleware in the main application.

Creating a Router is simple.

var express = require('express');
var myRouter = express.Router();

But how do you use it for something? The Router is a Middleware itself so you use it the same way you use any other Middleware.

var express = require('express');
var app = express();

var myRouter = express.Router();
app.use('/path', myRouter);

Of course, that Router won't really do anything. Let's add a path /cheese that we can GET. Here is a complete app.

var express = require('express');
var app = express();

var myRouter = express.Router();
myRouter.get('/cheese', function(req, res, next) {
    res.send(200, 'cheese is good');
});

app.use('/path', myRouter);

var server = app.listen(3000, function() {
    console.log('listening on port %d', server.address().port);
});

Now we're making progress. If you request /path/cheese you'll get the response "cheese is good." Which it certainly is.

That's great and all, but if you want to really perform some good modularization, the definition of the Router's unique functions should be moved into a separate file. So we would make a new file, cheese.js, that would look something like this.

module.exports = function(express) {

    var myRouter = express.Router();
    myRouter.get('/cheese', function(req, res, next) {
        res.send(200, 'cheese is good');
    });

    return myRouter;

};

And then your main app.js would look like this.

var express = require('express');
var cheese = require('./cheese');
var app = express();

app.use('/path', cheese(express));

var server = app.listen(3000, function() {
    console.log('listening on port %d', server.address().port);
});

And a request for /path/cheese should still tell you that cheese is good.

That's great and all, but here is the real benefit. You can add the same Router for multiple paths. Obviously, you could also reuse your cheese.js module in other applications as well. In the following example, I add the cheese router to both /path and /goat.

var express = require('express');
var cheese = require('./cheese');
var app = express();

var cheeseRouter = cheese(express);

app.use('/path', cheeseRouter);
app.use('/goat', cheeseRouter);

var server = app.listen(3000, function() {
    console.log('listening on port %d', server.address().port);
});

Now you should be able to GET /path/cheese and /goat/cheese. Goat cheese tastes much better than path cheese. Oh, what is path cheese? Well I'm sure you're familiar with leaving a trail of breadcrumbs. If you leave yourself a trail of bits of cheese you have path cheese.

My cheese example is highly simplified but if your module had a number of GETs and POSTs and stuff at different paths, the benefit of breaking it out into a separate file becomes much more clear. It also helps when some routes might need to import another module via require and no other routes need that module. Everything stays neat and self-contained.

Much easier to maintain.

Amphibian.com comic for September 12, 2014