Open-Source Wikis

/

Express

/

Features

/

Routing and middleware

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 prototypes

router.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:

  1. For each registered layer, check if the path matches.
  2. If it does, run the layer. The layer may be a route (with method-specific handlers) or pure middleware.
  3. Each layer calls next() or next(err) to advance.
  4. Error middleware (function (err, req, res, next)) is only invoked when next(err) was called.
  5. When the stack is exhausted, control returns to app.handle, which calls finalhandler to 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 (default false) — passed to the lazy Router({ caseSensitive }).
  • strict routing (default false) — passed as strict. Controls whether /foo and /foo/ match the same route.

Both must be set before the first app.use/app.METHOD call. After that, the router is already built.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Routing and middleware – Express wiki | Factory