Middleware — Patterns & Best Practices
📖 Concept
Middleware is the core architectural pattern of Express.js. A middleware function has access to the request (req), response (res), and the next function — which passes control to the next middleware in the stack.
Middleware signature:
function middleware(req, res, next) {
// Do something with req/res
next(); // Pass to next middleware
}
Types of middleware:
- Application-level —
app.use(fn)— runs for every request - Route-level —
app.get('/path', fn, handler)— runs for specific routes - Router-level —
router.use(fn)— runs for routes in that router - Built-in —
express.json(),express.static(),express.urlencoded() - Third-party —
cors(),helmet(),morgan(),compression() - Error-handling —
(err, req, res, next)— 4-parameter signature
Middleware execution order:
Request → middleware1 → middleware2 → route handler → Response
↓ (if error)
error middleware
Common middleware stack (production):
app.use(helmet()) // Security headers
app.use(cors()) // CORS
app.use(compression()) // Gzip compression
app.use(morgan('combined')) // Request logging
app.use(express.json()) // JSON body parsing
app.use(express.urlencoded(...)) // Form body parsing
app.use(rateLimit(...)) // Rate limiting
app.use('/api', authMiddleware) // Authentication
app.use('/api', routes) // Application routes
app.use(notFoundHandler) // 404 handler
app.use(errorHandler) // Error handler
🏠 Real-world analogy: Middleware is like an airport security process. Each checkpoint (middleware) inspects your luggage (request), adds a stamp (modifies req), and either lets you pass (next()) or stops you (res.status(403)). The order matters — you go through identity check (auth), then baggage scan (validation), then boarding (route handler).
💻 Code Example
1// Middleware — Patterns & Best Practices23const express = require("express");4const app = express();56// 1. Request logging middleware7function requestLogger(req, res, next) {8 const start = Date.now();910 // Capture the original res.end to measure response time11 const originalEnd = res.end;12 res.end = function (...args) {13 const duration = Date.now() - start;14 console.log(15 `[${new Date().toISOString()}] ${req.method} ${req.originalUrl} → ${res.statusCode} (${duration}ms)`16 );17 originalEnd.apply(this, args);18 };1920 next();21}2223// 2. Authentication middleware24function authenticate(req, res, next) {25 const token = req.headers.authorization?.replace("Bearer ", "");2627 if (!token) {28 return res.status(401).json({ error: "Authentication required" });29 }3031 try {32 // In production: verify JWT token33 // const decoded = jwt.verify(token, process.env.JWT_SECRET);34 const decoded = { id: 1, role: "admin" }; // Mock35 req.user = decoded; // Attach user to request36 next();37 } catch (err) {38 res.status(401).json({ error: "Invalid token" });39 }40}4142// 3. Authorization middleware (factory function)43function authorize(...roles) {44 return (req, res, next) => {45 if (!req.user) {46 return res.status(401).json({ error: "Not authenticated" });47 }48 if (!roles.includes(req.user.role)) {49 return res.status(403).json({50 error: `Requires one of roles: ${roles.join(", ")}`,51 });52 }53 next();54 };55}5657// 4. Request validation middleware (factory)58function validate(schema) {59 return (req, res, next) => {60 const errors = [];6162 for (const [field, rules] of Object.entries(schema)) {63 const value = req.body[field];6465 if (rules.required && (value === undefined || value === "")) {66 errors.push(`${field} is required`);67 }68 if (rules.type && value !== undefined && typeof value !== rules.type) {69 errors.push(`${field} must be a ${rules.type}`);70 }71 if (rules.minLength && value && value.length < rules.minLength) {72 errors.push(`${field} must be at least ${rules.minLength} characters`);73 }74 if (rules.pattern && value && !rules.pattern.test(value)) {75 errors.push(`${field} format is invalid`);76 }77 }7879 if (errors.length > 0) {80 return res.status(400).json({ error: "Validation failed", details: errors });81 }82 next();83 };84}8586// 5. Rate limiting middleware87function rateLimit({ windowMs = 60000, maxRequests = 100 } = {}) {88 const clients = new Map();8990 // Cleanup old entries periodically91 setInterval(() => {92 const now = Date.now();93 for (const [key, data] of clients) {94 if (now - data.startTime > windowMs) {95 clients.delete(key);96 }97 }98 }, windowMs);99100 return (req, res, next) => {101 const clientIP = req.ip || req.connection.remoteAddress;102 const now = Date.now();103104 if (!clients.has(clientIP)) {105 clients.set(clientIP, { count: 1, startTime: now });106 return next();107 }108109 const client = clients.get(clientIP);110111 if (now - client.startTime > windowMs) {112 clients.set(clientIP, { count: 1, startTime: now });113 return next();114 }115116 client.count++;117118 if (client.count > maxRequests) {119 res.setHeader("Retry-After", Math.ceil((windowMs - (now - client.startTime)) / 1000));120 return res.status(429).json({ error: "Too many requests" });121 }122123 next();124 };125}126127// 6. Error handling middleware (MUST have 4 parameters)128function errorHandler(err, req, res, next) {129 console.error("Error:", {130 message: err.message,131 stack: process.env.NODE_ENV === "development" ? err.stack : undefined,132 path: req.path,133 method: req.method,134 });135136 const statusCode = err.statusCode || 500;137 res.status(statusCode).json({138 error: err.isOperational ? err.message : "Internal server error",139 ...(process.env.NODE_ENV === "development" && { stack: err.stack }),140 });141}142143// === Apply middleware ===144app.use(requestLogger);145app.use(express.json());146147// Public routes148app.get("/api/health", (req, res) => {149 res.json({ status: "ok" });150});151152// Protected routes153app.use("/api/admin", authenticate, authorize("admin"));154155app.get("/api/admin/dashboard", (req, res) => {156 res.json({ message: "Admin dashboard", user: req.user });157});158159// Route with validation160app.post(161 "/api/users",162 authenticate,163 validate({164 name: { required: true, type: "string", minLength: 2 },165 email: { required: true, type: "string", pattern: /^[^@]+@[^@]+\.[^@]+$/ },166 }),167 (req, res) => {168 res.status(201).json({ data: req.body });169 }170);171172// Error handler (must be LAST)173app.use(errorHandler);174175app.listen(3000);
🏋️ Practice Exercise
Exercises:
- Write a request logging middleware that logs method, URL, status code, and response time
- Build an authentication middleware that verifies JWT tokens and attaches the user to
req.user - Create a role-based authorization middleware factory:
authorize('admin', 'moderator') - Implement a rate limiter using a Map-based sliding window counter
- Build a request validation middleware factory that validates
req.bodyagainst a schema - Chain multiple middleware on a single route: authenticate → authorize → validate → handler
⚠️ Common Mistakes
Forgetting to call
next()— the request hangs indefinitely because Express doesn't automatically pass to the next handlerPutting error-handling middleware before routes — Express processes middleware in order; error handlers must come last
Using 3-parameter functions for error handling — error handlers MUST have exactly 4 parameters:
(err, req, res, next)Not returning after sending a response in middleware — without
return, the code continues and may callnext()or send another responseAdding middleware after
app.listen()— middleware must be registered before the server starts accepting requests
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Middleware — Patterns & Best Practices. Login to unlock this feature.