Node.js Backend Fundamentals with Express · Build an API with Express
Middleware: Logging, Timing and Request IDs
Write middleware that runs before and after handlers, understand next() and ordering, and add request IDs and timing.
What you'll build
Three middleware functions: a request ID that every log line and response carries, a timer that records how long each request took, and an API key check on one router only.
The idea
- Middleware is a function
(req, res, next): do something, then callnext()to continue, or respond to stop the chain. - Middleware runs in registration order. Global middleware goes first; route-specific middleware is passed to the router or route.
- To act after the handler (for timing or logging), listen for the response's
finishevent. - Attach per-request data to
res.locals(orreq) for later middleware and handlers.
The code
import express from "express";
import { randomUUID } from "node:crypto";
const log = [];
const requestId = (req, res, next) => {
res.locals.id = req.get("x-request-id") ?? randomUUID().slice(0, 8);
res.set("x-request-id", res.locals.id);
next();
};
const timing = (req, res, next) => {
const start = performance.now();
res.on("finish", () => log.push(`${res.locals.id} ${req.method} ${req.originalUrl} ${res.statusCode} ${performance.now() - start < 50 ? "<50ms" : "slow"}`));
next();
};
const requireApiKey = (req, res, next) =>
req.get("x-api-key") === "secret-key" ? next() : res.status(401).json({ error: "missing or invalid API key" });
const admin = express.Router();
admin.use(requireApiKey);
admin.get("/stats", (req, res) => res.json({ orders: 42 }));
const app = express();
app.use(requestId, timing);
app.get("/health", (req, res) => res.json({ ok: true }));
app.use("/admin", admin);
const server = app.listen(0);
const base = `http://127.0.0.1:${server.address().port}`;
await fetch(`${base}/health`, { headers: { "x-request-id": "req-0001" } });
await fetch(`${base}/admin/stats`, { headers: { "x-request-id": "req-0002" } });
const ok = await fetch(`${base}/admin/stats`, { headers: { "x-request-id": "req-0003", "x-api-key": "secret-key" } });
console.log("response header x-request-id:", ok.headers.get("x-request-id"), JSON.stringify(await ok.json()));
server.close();
console.log(log.join("\n"));
Output
response header x-request-id: req-0003 {"orders":42}
req-0001 GET /health 200 <50ms
req-0002 GET /admin/stats 401 <50ms
req-0003 GET /admin/stats 200 <50msRun this on your computer — see “Get Started” for setup.
Common mistakes
- Forgetting to call
next()(the request hangs) or calling it after sending a response. - Registering logging middleware after the routes, so it never runs.
- Doing slow work (database calls) in global middleware for every request, including health checks.
Key points
- Middleware runs in order and either calls
next()or ends the request. - Use the
finishevent to act after the handler. - Scope middleware to routers when only some routes need it.
Further reading: Writing middleware · Using middleware
Practice questions
What happens if middleware neither responds nor calls next()?
Answer
The request hangs until the client times out, because nothing else in the chain runs.
How can middleware measure how long the handler took?
Answer
Record the start time, then listen for res.on('finish') and compute the duration when the response has been sent.
How do you apply middleware to only some routes?
Answer
Pass it to a Router (router.use) or directly to a route (app.get(path, mw, handler)) instead of app.use for everything.
Open in the app to mark lessons complete, take notes and track your progress.