Building Request Context
Building Request Context
Welcome back! In the previous lesson, you replaced repeated route-level parsing with reusable Zod schemas and validateRequest() middleware. Validated parameters, query strings, and bodies now flow through response.locals.validated, while the centralized error handler formats every ZodError consistently.
In this lesson, you’ll extend that middleware architecture in two directions. First, every request will receive shared context—a stable request ID and start timestamp—near the beginning of the Express pipeline. Second, only routes that consume JSON bodies will enforce Content-Type: application/json. You’ll also compare the two ways errors enter Express 5’s final error handler: rejected async promises and explicit next(error) calls.
Understanding Middleware Scope
Express middleware can be mounted globally or attached to specific routes.
Global middleware runs for every request:
Route-level middleware runs only where it is explicitly added.
This project follows a useful rule:
Request IDs are useful on every request, including successful responses, validation failures, missing routes, and internal errors. The request-context middleware therefore belongs near the beginning of the application.
JSON enforcement is different. Only routes that actually consume JSON bodies should require a JSON content type. Applying that rule globally would incorrectly reject ordinary GET and DELETE requests.
Creating a Request Context
The global middleware lives in src/middleware/request-context.ts.
-
request.header("x-request-id")reads an optional incoming trace identifier. HTTP header names are case-insensitive, so clients may display the name asX-Request-Id. -
.trim()removes surrounding whitespace. A non-empty client value is preserved, while a missing or whitespace-only value falls through torandomUUID(). -
response.locals.requestIdstores the selected identifier for the rest of the current request. Later middleware and route handlers can reuse it without reading the header again or generating another ID. -
response.locals.startedAtrecordsDate.now()when the request enters the application. It can later support request-duration logging or response metadata. -
The same request ID is echoed through the
X-Request-Idresponse header. A client can therefore correlate its request with logs or diagnostics on the server. -
next()passes control to the following middleware. This middleware establishes context but does not complete the response.
A custom incoming value such as:
is preserved exactly after trimming. Without the header, the response contains a generated UUID instead.
