ponyfoo.com

Modularizing Node Applications with Express

Fix

I’ve spent a few articles talking about build processes; now I want to spend a few words on application architecture, particularly in Node.JS web applications using Express.

In this article, I want to talk about the big picture: how to separate concerns of applications on different sub-domains in a clean manner.

Sometimes, you need to deal with requests on two separate sub-domains in your application, say www and blog, but more often than not, this happens within the same express application. While it’s certainly possible to handle the routing using a single module, I’ve come to realize it’s best to use a modular approach, similar to what connect.vhost does, but taken up a notch!

express.png
express.png

connect.vhost simply creates a middleware we can .use() in our application, passing it a new server instance for each host we want to handle. With it, we can set up a folder structure that looks somewhat like the following:

src
  - server.js
  + hosts
    + www
      - vhost.js
    + blog
      - vhost.js

With that folder structure in mind, we could code up our server.js file using:

'use strict';

var express = require('express');
var app = express();
var www = require('./hosts/www/vhost.js');
var blog = require('./hosts/blog/vhost.js');

app.use(www);
app.use(blog);
app.listen(3000, function(){
    console.log('listening on port 3000');
});

Thus, server.js would just contain the logic necessary to enable modularization, and nothing more. Before diving into one of our vhost.js files, lets figure out how to build a better vhost middleware. Here is what I propose:

'use strict';

function buildHostRegex(input){
    if (typeof input !== 'string'){
        return input;  
    }
    
    var hostname = input || '*';
    if (hostname.test(/[^A-z_0-9.*-]/)){
        throw new Error('Invalid characters in host pattern: ' + hostname);
    }

    var pattern = hostname
        .replace(/\./g, '\\.')
        .replace(/\*/g,'(.*)');

    var rhost = new RegExp('^' + pattern + '$', 'i');
    return rhost;
}

module.exports = function(factory){
    return function(hostname){
        var rhost = buildHostRegex(hostname);

        var middleware = function(req, res, next){
            if(middleware.on === false){
                return next();
            }

            if(!req.headers.host){
                return next();
            }

            var host = req.headers.host.split(':')[0];
            if(!rhost.test(host)){
                return next();
            }

            if (typeof middleware.server === 'function'){
                return middleware.server(req, res, next);
            }
            middleware.server.emit('request', req, res);
        };

        middleware.server = factory();

        middleware.off = function(){
            middleware.on = false;
        };

        middleware.on = function(){
            middleware.on = true;
        };

        return middleware;
    };
};

Using this module, this separation of concerns becomes almost trivial.

Our ./www/vhost.js implementation can become:

'use strict';

var express = require('express');
var vhost = require('virtual-host')(express);
var www = vhost('www.*');
var app = www.server;

app.use(function(req,res,next){
    res.send('response from www!');
});

module.exports = www;

The important factor in this separation is that we can handle views, static assets, routing, and pretty much every aspect of our application in a self-contained manner. We could always re-use common pieces of functionality through require, as per usual.

I can think of a few use cases for this sort of modularization

  • Setting up a sub-domain for API documentation, blog, or custom solution, while keeping our main application unaltered, when resorting to a third-party solution for one of our sub-domains
  • Setting up a request catch-all which is great for displaying a different application altogether for the initial setup of open-source projects, helping developers to set up their local environments
  • Setting up a maintenance catch-all which can be turned on and off as a service, allowing us to have an emergency off switch without necessarily turning off job queues, and allowing for a more responsive alternative to turn the server back online

How might the maintenance setup work? Probably something close to this:

'use strict';

var express = require('express');
var app = express();
var www = require('./hosts/www/vhost.js');
var maintenance = require('./hosts/maintenance/vhost.js');

app.use(maintenance);
app.use(www);
app.listen(3000, function(){
    console.log('listening on port 3000');
});

In the maintenance vhost.js:

'use strict';

var express = require('express');
var vhost = require('virtual-host')(express);
var maintenance = vhost('*'); // catch-all
var app = maintenance.server;

app.use(function(req,res,next){
    res.send('the server is in maintenance mode!');
});

maintenance.off(); // disabled by default

module.exports = maintenance;

We might have a background job that allows us to turn the maintenance vhost on or off, and that would be it. What’s even better, it would be reusable in any applications we craft following this pattern.

If you want to read more articles like this, follow the RSS feed, or subscribe to the email updates!

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (3)

Amrit Bera wrote

Nice writeup! But curious to know whats your take on nginx doing the same thing? Moreover, it can serve the static files(public/*) directly without bothering node.

Nicolas Bevacqua wrote

If you can get away with using nginx, you most definitely should. For setups where having a pure Node installation is preferred, for whatever reason, I believe this would be a decent approach.

The grand idea was to illustrate the fact that separation of concerns is just as important in Node as in any other backend language.

Just because it’s JavaScript, it doesn’t mean we have to write messy, all-over-the-place code.

Amrit Bera wrote

Makes sense… Thanks!