Date Thu 01 May 2014 Tags Express

A bit of background...

In some ways, express is very modular, just like node itself. You can easily bring in any of a large collection of standard modules to help in building your applications, and many of them are well known to anyone working with node.js. Your dependencies can manage their own dependencies, it's all very civilized.

This all starts to fall apart when you want to build a set of nested modules that expect to handle requests for part of the overall path (routes, in express terminology). Here's a blog that covers some of the pitfalls.

In summary, you need to wire your routes (which control which code handles which path) together entirely from the top, and either:

  1. Configure all your routes from your main app.js file.
  2. Pass the top-level app object to a module and let the module add its own paths (and hopefully be smart enough to avoid path conflicts while doing so).

So, if it's not possible, I assume you wouldn't even be writing about it...

The thing is, those limitations were very much true of the 3.x version of express, but now that express 4 is available, there is a much better option. Express 4 adds the concept of an explicit router object, and makes it easy to create nested modules that add their own routes relative to their parent.

Here's just one way you can use it:

  1. Let's say that you are a lazy programmer (you'd be in good company). You have an API that needs (among other things) to have user management functions.
  2. Someone has already written a third-party library (express-couchUser) that handles user management, and you want to use that instead of writing your own.
  3. Your app.js file requires and instantiates your API module (let's call it api) and associates that with the path /api
  4. Your api module requires express-couchUser and associates that with the path /user.
  5. express-couchUser becomes available under /api/user instead of /user.

That's just a simple example of why you'd want such a thing. There are many other benefits that help with managing your code over time. Let's say (like me) you're working on an API. As your API matures, you can move things around cleanly. If tomorrow you decide to start having versioned API paths, you can easily move your existing API to /api/v1/, and all of the versioned paths will continue working without any changes in lower-level modules. With this approach , you can reuse whatever modules that make sense when creating /api/v2/, or even when quickly setting up legacy links for the original non-versioned API.

Finally, because the modules work with relative paths, you can test every module in complete isolation from other modules. This is much saner for testing. It doesn't matter that you'll add the module three levels deep in the path in your final app. You just write a simple test that mounts your module at the top level and fire away.

You can manage the versions for each module separately, keeping each module clean and small and focused on a clear part of the problem.

Code or it didn't happen...

So, how do you make this work? Let's start with the simplest app.js imaginable:

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

var config = { "app": "sample application" };

var child = require('./child')(config);
app.use('/child',child);

app.use("/",function(req, res) {
    res.send("Hello from the root of " + config.app + ".\n");
});

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

Now let's look at the contents of ./child/index.js (which could just as easily be a module):

module.exports = function(config) {
    var express = require('express');
    var router = express.Router();

    var grandchild = require('./grandchild')(config);
    router.use('/grandchild', grandchild);

    // TODO:  Add a redirect to the api docs if someone request the root of the API, or at least an informative note.
    router.use("/",function(req, res) {
        res.send("Hello from the child of " + config.app + ".");
    });

    return router;
}

And here's the "grandchild" module, ./child/grandchild/index.js:

module.exports = function(config) {
    var express = require('express');
    var router = express.Router();

    router.use("/",function(req, res) {
        res.send("Hello from the grandchild of " + config.app + ".");
    });

    return router;
}

When you run node app, each level returns the expected output:

$ curl http://localhost:5972/
Hello from the root of sample application.

$ curl http://localhost:5972/child/
Hello from the child of sample application.

$ curl http://localhost:5972/child/grandchild
Hello from the grandchild of sample application.

Note that the syntax remains the same all the way down. You just worry about passing things to the next link in the chain, and not about what the whole chain looks like. As I needed to do so when developing this approach, I included the passing of a top-level config object as an example of how the chaining ideally works.

Just in case you'd like to try it out on your own, I've published the source for this tutorial on GitHub.

A few more notes...

Here are a few things I found helpful in using this technique to modularize my existing express app.

The new router object supports the same HTTP method shortcuts as the app object in express, so you can adapt old code by replacing your old app variable with a router in many cases. The only exception is the DELETE method, which is aliased to app.del, but is available as router.delete. The latter makes more sense anyway, as it actually matches the HTTP method.

Also, although not pictured here, I have also seen modules that use an express "app" object instead of a router. That approach works fine in 4.x, and has the added benefit of letting you easily use another templating language in a module without rolling your own.

As a final caveat, upgrading to Express 4.x is not all that difficult, but does require some follow through. For starters, a lot of modules have been moved out of express/connect, which means you have to explicitly bring them in. For a good primer, check out this page:

http://scotch.io/bar-talk/expressjs-4-0-new-features-and-upgrading-from-3-0

You can also read the links provided when you try to run express with an old module, which are pretty descriptive, and link to documentation. If you follow the right link, there's even a quick list of equivalents for the old built-ins:

https://github.com/senchalabs/connect/blob/master/Readme.md#middleware

Anyway, I hope that's useful for people who are also trying to work with nested modules in express.


Comments

comments powered by Disqus