Middleware Levels

Application, Router and Route-Level Middleware

The same function can be attached at several scopes, and the scope decides how many requests pay for it. Bind at the narrowest scope that still covers every request needing the behavior: a body parser mounted globally parses JSON for your health check too.

The same check attached at three scopesJavaScript
app.use(requestTimer);                        // application: every request
app.use('/api', requireApiKey({ keys }));     // application, path-mounted
const books = express.Router();
books.use(express.json({ limit: '32kb' }));   // router: every route in this router
books.param('id', (req, res, next, id) => {   // router: only routes with :id
  req.bookId = Number(id);
  next();
});
books.post('/', validateBook, createBook);    // route: this one endpoint
app.use('/api/books', books);
Where a middleware function can be attached and what it costs
Scope Registered with Runs for Typical use
Application app.use(fn) Every request Logging, request id, helmet 10,736
Path-mounted app.use('/api', fn) Paths under the prefix API keys, CORS for one area
Router router.use(fn) Routes in that router Body parsing, auth for a resource
Parameter router.param('id', fn) Routes containing :id Loading a record once
Route app.get(p, fn, handler) One method and path Validation, upload handling

router.param is the scope people forget. Registering it once replaces the "parse the id, look it up, 404 if missing" preamble in every handler that takes an :id, it runs before any route-level middleware on the matched route, and it fires only once per request even when two layers match the same parameter.

Mount paths are stripped, not just matched. Inside a router mounted at /api/books, req.url is /42 while req.originalUrl is /api/books/42, and req.baseUrl holds the prefix. Log req.originalUrl; route on req.url.