Middleware
The chain of responsibility. Order matters. Single-responsibility middleware.
What Middleware Is
Middleware is a function that sits between the incoming request and the final handler. It intercepts, transforms, or rejects the request (and optionally the response).
The concept: Request → [MW1] → [MW2] → [MW3] → Handler → [MW3] → [MW2] → [MW1] → Response
Each middleware can:
1. Execute any code
2. Modify req/res objects
3. End the request-response cycle (e.g., return 401)
4. Call next() to pass to the next middleware
5. Not call next() to halt the chain
This is the Chain of Responsibility pattern.
Common Middleware Types
Logging middleware — logs every request (method, path, status, duration). Should be first.
Authentication middleware — validates token/session. Returns 401 if invalid. Attaches user to request.
Authorization middleware — checks permissions after auth. Returns 403 if insufficient.
Rate limiting middleware — counts requests per client, rejects with 429 if exceeded.
CORS middleware — adds cross-origin headers, handles preflight OPTIONS requests.
Body parsing middleware — deserializes JSON/form body from raw bytes.
Error handling middleware — catches errors thrown by handlers, formats them into proper error responses. Should be last.
Compression middleware — gzip/brotli compresses response body.
Timeout middleware — returns 503 if the handler takes too long.
Middleware Order Matters
Middleware executes in registration order. Wrong order = subtle bugs.
Correct order for a typical API:
1. Request logging (needs to run before everything)
2. CORS (must be before auth — preflight shouldn't require auth)
3. Rate limiting
4. Body parsing (auth middleware needs the parsed body)
5. Authentication
6. Request-specific middleware (per-route)
7. Your route handlers
8. Error handling (must be last)
A classic mistake: registering error middleware before routes. Errors from routes won't reach it.
Writing Clean Middleware
// Express-style middleware signature
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "No token" });
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload; // attach to request for downstream use
next(); // proceed to next middleware/handler
} catch (err) {
res.status(401).json({ error: "Invalid token" });
// Note: no next() called — chain stops here
}
}
Key rules:
• Always call next() OR send a response — never both
• Always call next(err) to pass errors to error handler
• Keep middleware single-responsibility
• Avoid heavy computation in middleware (it runs on every request)
The Backend from First Principles series is based on what I learnt from Sriniously's YouTube playlist — a thoughtful, framework-agnostic walk through backend engineering. If this material helped you, please go check the original out: youtube.com/@Sriniously. The notes here are my own restatement for revisiting later.