Code Review: Express

04 Jul 2011 | By Alex Young | Tags code-review express frameworks node

Code Review is a series on DailyJS where I take a look at an open source project to see how it’s built. Along the way we’ll learn patterns and techniques by JavaScript masters. If you’re looking for tips to write better apps, or just want to see how they’re structured in established projects, then this is the tutorial series for you.

I was recently looking for something in the source to Express, and suddenly it struck me that this would make a great candidate for a code review. Let’s take a look at how Express is put together.

About Express

Express (GitHub: visionmedia / express, License: MIT, npm: express) by TJ Holowaychuk is an extremely popular web framework. It’s fast, simple, and easy to learn, and even better — it’s actively maintained! Strolling through npm looking at web frameworks is a little bit like a graveyard of half-finished ideas, but Express has received much love from its author, contributors, and the Node community.

Usage

Express actually comes with a command-line app that can be used to generate a basic app. I usually just write my own, it’s very easy:

var express = require('express')
  , app = express.createServer();

app.get('/', function(req, res) {
  res.send('Hello from DailyJS');
});

app.listen(3000);

Structure

The main lib/express.js starts off by loading Connect. Express is built on the Connect library which provides a number of features we’d typically expect from basic web framework: http and https servers, sessions, cookies, HTTP auth, static asset management, query string parsing, and more.

The http and https servers in Express wrap the ones from Connect to provide some Express-specific features, like routing and middleware. Connect’s servers are in turn derived from Node’s.

The rest of the file loads other pieces of key functionality and exposes them using exports as required.

Moving on to lib/http.js, we can see that an Express “app” is really a little server. When a HTTPServer is instantiated, it runs app.init which sets up simple objects for things like the cache, settings, and routes, then sets defaults. Some of this default behaviour is based on the environment, so for example, the production environment gets caching enabled.

The initialisation code also heavily reuses features we typically associate with the public Express API. The use method allows Express to be extended with middleware, and it returns this so it’s chainable. In fact, chaining is an interesting point because most methods in the server code return this. I don’t usually see this being used in Express apps.

This code also shows how routing really works. The whole server is heavily dependent on the router, which is set up in app.init:

this.routes = new Router(this);

This Router object represents a collection of routes, and it also tracks URL parameters so route param pre-conditions can be handled (this is a very useful feature).

Routes

The route library is loaded like this:

var router = require('./router');

This loads router/index.js (see Folders as Modules) which contains collection-related routing methods. Routes can be added, removed, searched, and actually dispatched.

The core route finding method is Router.prototype.find. There are convenience functions for this, namely Router.prototype.lookup and Router.prototype.match. The naming makes sense here because lookup matches text and match uses regular expressions.

Results are built using a class called Collection which is based on Array, but with some custom initialization and a remove method. The body of find just runs a callback over each route using a simple for loop.

There’s also a Route class. Routes are made up of a HTTP method, path, and some additional options. The options can include a parameter for route strictness:

- strict: enable strict matching for trailing slashes

Path strings are turned into regular expressions using normalize(). This uses some nicely managed regular expression grouping to extract parameters, formats, and other URL options.

One detail about route handling is HTTP verbs are delegated out in the HTTP server like this:

methods.forEach(function(method){
  app[method] = function(path){
    if (1 == arguments.length) return this.routes.lookup(method, path);
    var args = [method].concat(toArray(arguments));
    if (!this.__usedRouter) this.use(this.router);
    return this.routes._route.apply(this.routes, args);
  }
});

During initialisation a getter is set up for this.router:

this.routes = new Router(this);
this.__defineGetter__('router', function(){
  this.__usedRouter = true;
  return self.routes.middleware;
});

Going back to lib/router/index.js:

function Router(app) {
  var self = this;
  this.app = app;
  this.routes = {};
  this.params = {};
  this._params = [];

  this.middleware = function(req, res, next){
    self._dispatch(req, res, next);
  };

We can see that self.routes.middleware will call the dispatcher.

Views

Moving on to views… lib/view/view.js provides a simple interface to views that helps manage paths and rendering engines. Partial template support is even more surprisingly simple, with lib/view/partial.js containing just one function that lib/view.js uses to determine which view to render.

Request and Response

The req and res objects, ubiquitous in Express apps, wrap around http.IncomingMessage.prototype and http.ServerResponse.prototype which ultimately comes from Node’s http module via Connect.

The res.send method does a lot of the work for generating responses. The data passed in is checked using switch (typeof body) and this.header('Content-Type'). Good old JSON.stringify is used to generate JSON responses.

Style

Throughout Express TJ places literal values first in if statements:

if ('string' != typeof route) {
  middleware = route, route = '/';
}

I assume this is because accidentally typing if ('value' = a) will cause an exception to be thrown.

Another style observation I made (beyond those I’ve already explored in TJ’s Jade project) was a heavy reliance on native functionality. I expected to see a fairly heavy route collection management class, but TJ has simply built on Array.prototype.

This is also another project that includes lots of well formatted code comments, so it’s easy to understand the author’s intent in the more abstract corners of the project.

Conclusion

I hope this tour of the Express source has given you a feel of how it’s put together, and also what it takes to build a web framework with Node.

I invite you to take a look around yourself! If you’re looking for help when developing your own Node apps, Express contains a lot of pointers. It’s also worth looking at how these underlying mechanisms work if you’re convinced you’ve exhausted the Express Guide documentation.


blog comments powered by Disqus