This page traces one HTTP request end to end through the actual code paths in lib/. Two facts make the whole design legible: an Express app is a function with the Node request-handler signature, and everything after app.handle is delegation -- to the router package for dispatch and to finalhandler when nothing else responds.
The app is the server callback
createApplication() in lib/express.js builds a plain function and mixes the application prototype onto it:
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
// …
app.init();
return app;
}
app.listen is then a three-line convenience: create a Node http.Server with the app as its callback and listen. (The extra lines wrap a final callback with once so it also fires on server errors such as EADDRINUSE -- a guarantee tested in test/app.listen.js.)
app.listen = function listen() {
var server = http.createServer(this)
var args = slice.call(arguments)
if (typeof args[args.length - 1] === 'function') {
var done = args[args.length - 1] = once(args[args.length - 1])
server.once('error', done)
}
return server.listen.apply(server, args)
}
Because the app is just a handler function, you can also pass it to https.createServer({...}, app) or mount it inside another app -- nothing in Express assumes it owns the server.
One request, start to finish
Every node in this diagram is a call you can find in the code: http.Server invokes the app function, which runs app.handle (lib/application.js), which delegates to router.handle from the router package, which walks the layer stack until a handler ends the response or control falls through to finalhandler.
sequenceDiagram
participant C as Client
participant S as http.Server
participant A as app.handle (lib/application.js)
participant R as router.handle (router pkg)
participant M as middleware layers
participant H as route handler
participant F as finalhandler
C->>S: HTTP request
S->>A: app(req, res)
A->>A: set X-Powered-By, setPrototypeOf(req, res)
A->>R: this.router.handle(req, res, done)
R->>M: matching layers, in registration order
M->>M: next() advances / next(err) skips to arity-4
M->>H: first matching route handler
alt handler responds
H->>C: res.send / res.json / res.end
else nothing matched or next(err) fell through
R->>F: done(err)
F->>C: 404 or error status
end
Inside app.handle
app.handle does four things before handing off: build the fallback done callback, set the X-Powered-By header, swap the prototypes of Node's raw req/res objects for Express's extended ones, and call the router.
app.handle = function handle(req, res, callback) {
// final handler
var done = callback || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
// …
// alter the prototypes
Object.setPrototypeOf(req, this.request)
Object.setPrototypeOf(res, this.response)
// …
this.router.handle(req, res, done);
};
The prototype swap is the entire mechanism behind req.query, res.json, and every other Express extension -- covered in request and response extensions.
The callback parameter is what makes apps nestable: when an app is mounted inside another, the parent passes a next function here, so errors and misses continue in the parent instead of hitting finalhandler. Only the outermost app creates the finalhandler fallback.
Dispatch and the two exits
The router (see routing and middleware) walks its stack of layers -- path-matched middleware and routes -- calling each with (req, res, next). There are exactly two ways out:
- A handler responds.
res.send,res.json,res.render,res.end-- the response is written and later layers never run. - Control falls off the end. Every layer called
next()(ornext(err)and no error handler consumed it). The router callsdone, andfinalhandlerwrites a 404 -- or, for an error, the error's status with a body that is stack trace in development and status message in production (it reads theenvoption passed above).
Registration order is the only ordering rule. test/middleware.basic.js pins this: two app.use calls record ['one', 'two'], never the reverse. The error path -- how next(err) skips ordinary layers and finds four-argument handlers -- is specified in test/Route.js and walked through in routing and middleware.