What you'll learn
Middleware turns cross-cutting HTTP behavior into an explicit pipeline. You will compose request identity, timing, validation, and authorization boundaries without hiding domain rules or losing control flow.
By the end of this lesson, you'll be able to:
- Trace execution through the middleware stack
- Create request-scoped context and timing
- Build reusable validation middleware
- Place authentication and authorization at clear boundaries
Core mental model
Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.
| Concept | What it means | Decision rule |
|---|---|---|
| Middleware | A function that observes or changes the request-response cycle | Use for cross-cutting transport concerns, not core domain rules |
| next() | Transfers control to the next matching layer | Call once only when this middleware has not ended the response |
| Request context | Safe per-request metadata such as request ID and principal | Attach namespaced values early and avoid global mutable state |
| Middleware factory | Configuration creates a reusable middleware function | Use for parameterized validation and authorization policies |
Professional workflow
Build and verify Node.js programs from the terminal in small, observable steps.
- List cross-cutting concerns and their required order
- Assign a request ID before logging
- Parse before validating
- Authenticate before authorization
- Store validated input separately from raw input
- Mount resource routes
- Handle not-found and errors last
- Test short-circuit and next paths
Keep the feedback loop short
Guided code lab
Add request identity and completion logging
finish observes the completed response. The request ID connects inbound, application, and error logs.
import { randomUUID } from "node:crypto";
export function requestContext(request, response, next) {
request.context = { requestId: request.get("x-request-id") ?? randomUUID() };
response.set("x-request-id", request.context.requestId);
const started = performance.now();
response.on("finish", () => {
console.log(JSON.stringify({
requestId: request.context.requestId,
method: request.method,
path: request.originalUrl,
status: response.statusCode,
durationMs: Math.round(performance.now() - started),
}));
});
next();
}Build a validation middleware factory
The validator returns normalized data or issues. Downstream handlers consume validatedBody rather than reparsing raw input.
export function validateBody(validator) {
return function validationMiddleware(request, response, next) {
const result = validator(request.body);
if (!result.ok) {
response.status(422).json({
error: { code: "VALIDATION_FAILED", issues: result.issues },
});
return;
}
request.validatedBody = result.value;
next();
};
}Compose the pipeline intentionally
Order is behavior. Each layer receives the guarantees created by earlier layers.
app.use(requestContext);
app.use(express.json({ limit: "100kb" }));
app.post(
"/api/tasks",
requireAuthentication,
requirePermission("tasks:create"),
validateBody(validateNewTask),
createTaskHandler,
);
app.use(notFoundHandler);
app.use(errorHandler);Production practice
Make order visible
Keep top-level registration readable as a pipeline; avoid modules that secretly register global middleware.
Short-circuit clearly
After sending a rejection response, return instead of calling next or continuing execution.
Keep policies focused
Authentication establishes identity; authorization decides whether that identity may perform this operation.
Common failure mode
Independent workshop
Add a production-shaped middleware pipeline to the project API.
Your finished workshop must include:
- Request IDs echoed in responses
- Completion logs with status and duration
- Reusable body and parameter validators
- Separate authentication and permission checks
- Tests proving both short-circuit and success ordering
Definition of done
Recap & quick check
Key takeaways
- Middleware order changes behavior
- next transfers control
- Cross-cutting transport concerns fit middleware
- Validated data should be distinct from raw input
- Authentication and authorization are different boundaries
- Request context must be request-scoped
Quick check
1. What must middleware do if it does not end the response?
2. Which normally comes first?
3. Where should a request ID be stored?
4. What should happen after middleware sends a 422 response?
Next: Configuration, Errors & Observability