expressjs/express
Routing and middleware
Active contributors: dougwilson, ulisesgascon, wesleytodd
Express delegates almost all of its routing logic to the external router package. The Express side of the boundary is thin: app.use, app.METHOD, app.route, app.param, app.all, and the lazy app.router getter. This page walks the boundary.
What lives where
| Concern | Location |
|---|---|
app.use, app.METHOD, app.all, app.route, app.param, lazy app.router |
lib/application.js |
The actual Router class, route matching, parameter handling, error handling, middleware stack execution |
External router npm package |
Route constructor |
External router package, re-exported as express.Route |
Router constructor |
External router package, re-exported as express.Router |
| Mount semantics + parent prototype inheritance | lib/application.js ('mount' event listener) |
The router is constructed lazily the first time app.router is read:
// lib/application.js (excerpt)
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;
},
});That means changes to case sensitive routing or strict routing after the first app.use/app.METHOD call have no effect — the router is already built.
Registering middleware
app.use(...) accepts:
- Just a middleware:
app.use(fn) - A path + middleware(s):
app.use('/admin', fn1, fn2) - A path + array of middlewares:
app.use('/api', [fn1, fn2])(flattened internally) - A path + sub-app:
app.use('/admin', subApp)
Internally, app.use flattens the args, then delegates each function to router.use(path, fn) — except when a function looks like an Express app (has both .handle and .set). In that case, the sub-app is wrapped to restore the parent's request/response prototypes after next and the 'mount' event is fired on the sub-app.
sequenceDiagram
participant App as parentApp.use
participant SubApp
participant Router as parentApp.router
App->>App: detect fn.handle && fn.set -> sub-app
App->>SubApp: subApp.mountpath = path
App->>SubApp: subApp.parent = parentApp
App->>Router: router.use(path, mounted_app wrapper)
App->>SubApp: subApp.emit('mount', parentApp)
SubApp->>SubApp: 'mount' listener inherits<br/>request/response/engines/settings prototypesrouter.use(path, mounted_app) puts the wrapper on the parent's stack. When a request matches, the wrapper invokes subApp.handle(req, res, next), restoring req.app.request / req.app.response prototypes when the sub-app's next runs.
Registering routes
app.METHOD(path, ...handlers) is generated for every HTTP method:
methods.forEach(function (method) {
app[method] = function (path) {
if (method === 'get' && arguments.length === 1) {
return this.set(path);
}
var route = this.route(path);
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});Each call creates (or fetches) a Route instance via this.router.route(path) and registers handlers on it for the given method.
app.all(path, ...handlers) registers handlers for every method on the same Route.
app.route(path) is the explicit equivalent — useful for chaining:
app.route('/users/:id').get(getUser).put(updateUser).delete(deleteUser);Parameter handlers
app.param('id', loadUser) registers a callback that runs whenever a route with :id matches. Multiple names can be passed as an array. Internally, app.param simply iterates and calls this.router.param(name, fn).
Modular routers
express.Router() (re-exported from the router package) creates a sub-router that can be mounted via app.use(path, router). Behaviour-wise, mounted routers participate in the same stack execution but are not full sub-apps — they share their parent's settings, request/response prototypes, etc.
For full app isolation (independent settings, separate engines), nest entire Express apps with app.use(path, otherApp).
Stack execution and errors
The actual stack walking is in the router package. Conceptually:
- For each registered layer, check if the path matches.
- If it does, run the layer. The layer may be a route (with method-specific handlers) or pure middleware.
- Each layer calls
next()ornext(err)to advance. - Error middleware (
function (err, req, res, next)) is only invoked whennext(err)was called. - When the stack is exhausted, control returns to
app.handle, which callsfinalhandlerto either 404 or format the error.
finalhandler (the package) is configured per-request in app.handle:
var done =
callback ||
finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this),
});In production it strips error stacks; in development it surfaces them. The logerror callback writes to console.error unless env === 'test'.
Settings that affect routing
case sensitive routing(defaultfalse) — passed to the lazyRouter({ caseSensitive }).strict routing(defaultfalse) — passed asstrict. Controls whether/fooand/foo/match the same route.
Both must be set before the first app.use/app.METHOD call. After that, the router is already built.
Related pages
- Application for the lazy router and the mount semantics.
- Glossary for the
router,route,mount, andmountpathdefinitions. - Reference / Configuration for the routing-related settings.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.