DailyJS

Code Review: Express

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

frameworks node express code-review

Code Review: Express

Posted by Alex R. Young on .
Featured

frameworks node express code-review

Code Review: Express

Posted by Alex R. Young on .
*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
.