express() returns a function that has been dressed up as an object. This page covers how that assembly works, how settings are stored and compiled, and the mounting machinery behind app.use(subapp).
Assembly
The pieces and how they connect -- every edge below is a line in lib/express.js or lib/application.js:
flowchart TD
E["express() — lib/express.js"] --> F["app = function(req,res,next)"]
F --> M1["mixin EventEmitter.prototype"]
F --> M2["mixin application proto — lib/application.js"]
F --> RQ["app.request = Object.create(lib/request.js)"]
F --> RS["app.response = Object.create(lib/response.js)"]
F --> I["app.init()"]
I --> S["settings / engines / cache (null-prototype objects)"]
I --> LR["lazy router getter — new Router() on first access"]
LR --> RT["router package"]
Two details in this diagram carry most of the design:
Per-app request/response prototypes. app.request and app.response are per-app objects created with Object.create over the shared prototypes in lib/request.js and lib/response.js, with an app property baked in:
// expose the prototype that will get set on requests
app.request = Object.create(req, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
This is why req.app works inside any handler, and why two apps in one process can extend req/res independently.
The router is created lazily. app.init defines a getter instead of constructing the router, because the router's constructor options come from settings the user has not set yet:
Object.defineProperty(this, 'router', {
configurable: true,
enumerable: true,
get: function getrouter() {
if (router === null) {
router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabled('strict routing')
});
}
return router;
}
});
Consequence worth knowing: case sensitive routing and strict routing only take effect if set before the first route is registered, since the first app.use/app.get call materializes the router.
Settings: stored raw, compiled on write
app.set(key, value) writes into this.settings, but three keys trigger immediate compilation into a companion * fn setting:
switch (setting) {
case 'etag':
this.set('etag fn', compileETag(val));
break;
case 'query parser':
this.set('query parser fn', compileQueryParser(val));
break;
case 'trust proxy':
this.set('trust proxy fn', compileTrust(val));
// …
}
The compilers live in lib/utils.js. compileTrust shows the pattern -- every accepted shape (boolean, hop count, CIDR list, function) becomes a function so the hot path in lib/request.js never branches on type:
exports.compileTrust = function(val) {
if (typeof val === 'function') return val;
if (val === true) {
// Support plain true/false
return function(){ return true };
}
if (typeof val === 'number') {
// Support trusting hop count
return function(a, i){ return i < val };
}
// …
return proxyaddr.compile(val || []);
}
An invalid value throws at set time, not at request time (compileETag: throw new TypeError('unknown value for etag function: ' + val)). The full key list with defaults is in the app settings reference.
Mounting: sub-apps inherit through prototype chains
app.use(path, subapp) detects an Express app by duck-typing (fn.handle && fn.set), records mountpath and parent, wraps it so the child's prototypes are restored on the way out, and emits mount on the child:
debug('.use app under %s', path);
fn.mountpath = path;
fn.parent = this;
// restore .app property on req and res
router.use(path, function mounted_app(req, res, next) {
var orig = req.app;
fn.handle(req, res, function (err) {
Object.setPrototypeOf(req, orig.request)
Object.setPrototypeOf(res, orig.response)
next(err);
});
});
// mounted an app
fn.emit('mount', this);
The child's mount listener (registered in defaultConfiguration) then chains the child's settings, engines, request, and response objects onto the parent's via Object.setPrototypeOf. That is the whole inheritance mechanism: a child app reads the parent's settings through the prototype chain until it shadows a key with its own set. test/config.js pins both directions ("should default to the parent app", "should given precedence to the child").
trust proxy gets special back-compat handling: a symbol-keyed flag records whether the child ever set it explicitly, and if not, the child's compiled default is deleted so the parent's trust function shows through.
Everything else is delegation
The remaining app surface is thin proxies to the router: app.route(path) calls this.router.route(path), app.param forwards to router.param (adding array support), and the verb methods are generated in a loop over Node's http.METHODS:
methods.forEach(function (method) {
app[method] = function (path) {
if (method === 'get' && arguments.length === 1) {
// app.get(setting)
return this.set(path);
}
var route = this.route(path);
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
The app.get overload -- one argument reads a setting, two or more registers a GET route -- lives right here.