router (^2.2.0)

Imported by: lib/express.js and lib/application.js (var Router = require('router')).

In Express 4, routing was ~1,500 lines under lib/router/. Express 5 deleted that directory and depends on the standalone router package, maintained by the same pillarjs organization. The motive is shared maintenance: path matching (via path-to-regexp), layer dispatch, and param handling get fixed once, and any Connect-style framework -- not just Express -- benefits. For this repo it means the entire dispatch engine described in routing and middleware is an external contract, held in place locally by test/Router.js (600+ lines) and test/Route.js.

The two touch points in this repo:

lib/express.js
exports.Route = Router.Route;
exports.Router = Router;

express.Router() is literally the dependency's constructor, re-exported. And lib/application.js constructs the app's base router lazily, feeding it two app settings:

lib/application.js
router = new Router({
  caseSensitive: this.enabled('case sensitive routing'),
  strict: this.enabled('strict routing')
});

Everything else the app does with routes -- app.use, app.route, app.param, the verb methods -- is a forward to this object. Note the version boundary this creates: route pattern syntax (:param, *splat, {/:optional}) is defined by router/path-to-regexp, not by code in this repo. The Express 5 syntax visible in examples, e.g. app.all('/user/:id{/:op}', ...) in examples/route-separation/index.js, is path-to-regexp v8 syntax surfaced through router@2.

finalhandler (^2.1.0)

Imported by: lib/application.js only.

finalhandler answers the question every middleware framework must: what happens when no layer responds? app.handle installs it as the done callback for the outermost app:

lib/application.js
var done = callback || finalhandler(req, res, {
  env: this.get('env'),
  onerror: logerror.bind(this)
});

When the router's stack is exhausted, finalhandler writes a 404; when it is invoked with an error, it writes the error's status (defaulting to 500), including the stack trace in the body only when env is not production. The onerror hook is Express's logerror, which console.errors unless env is test -- which is why the test suite (running with NODE_ENV=test via test/support/env.js) stays quiet through hundreds of deliberately thrown errors.

Because mounted apps receive a real callback from their parent, only the top-level app ever constructs a finalhandler -- misses inside a sub-app continue in the parent's stack instead of 404ing early. That behavior is what makes examples/vhost and examples/multi-router composable.

The supporting cast in lib/express.js and lib/application.js

Three small packages hold the factory together, all visible in the first 30 lines of each file:

  • merge-descriptors (^2.0.0) -- mixin(app, proto, false) in createApplication copies the application prototype onto the app function using property descriptors, preserving getters (plain Object.assign would evaluate them). The false means existing keys are not overwritten.
  • once (^1.4.0) -- wraps the app.listen callback so it fires exactly once whether the outcome is listening or error (test/app.listen.js, "should callback on HTTP server errors").
  • debug (^4.4.0) -- namespaced trace logging: express:application here, express:view in lib/view.js. DEBUG=express:* node app.js prints setting writes, mount events, and view lookups; zero cost when the env var is unset.

Sources: lib/express.js, lib/application.js, test/Router.js · last synced 2026-07-27 · a371447 · version 5.2.1