Express.js Middleware Guide
Comprehensive reference for Express.js middleware patterns including built-in, third-party, and custom middleware with practical code examples.
1. Middleware Basics — app.use()
Middleware functions have access to req, res, and next. Call next() to pass control to the next middleware.
const express = require('express');
const app = express();
// Application-level middleware (runs for every request)
app.use((req, res, next) => {
console.log(`${req.method} ${req.path} — ${Date.now()}`);
next(); // must call next() or send a response
});
// Path-scoped middleware (only runs for /api/*)
app.use('/api', (req, res, next) => {
req.apiVersion = 'v1';
next();
});
// Multiple middleware functions in one call
app.use(express.json(), express.urlencoded({ extended: true }));
app.listen(3000);
2. Custom Middleware Patterns
Request Logger
// middleware/logger.js
function requestLogger(options = {}) {
const { format = 'combined' } = options;
return (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
});
next();
};
}
module.exports = requestLogger;
// Usage
app.use(requestLogger({ format: 'short' }));
Authentication Middleware
// middleware/auth.js
const jwt = require('jsonwebtoken');
function requireAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
try {
const token = authHeader.slice(7);
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
}
// Role-based guard factory
function requireRole(...roles) {
return (req, res, next) => {
if (!roles.includes(req.user?.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
module.exports = { requireAuth, requireRole };
// Usage
app.get('/admin', requireAuth, requireRole('admin'), (req, res) => {
res.json({ message: 'Welcome admin' });
});
3. Error-Handling Middleware
Error middleware takes 4 parameters: (err, req, res, next). It must be registered last.
// Async error wrapper — avoids try/catch in every route
const asyncHandler = fn => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
// Custom error class
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
// Route using asyncHandler
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new AppError('User not found', 404);
res.json(user);
}));
// Global error handler (must be last app.use)
app.use((err, req, res, next) => {
const status = err.statusCode || 500;
const message = err.isOperational ? err.message : 'Internal server error';
if (process.env.NODE_ENV === 'development') console.error(err.stack);
res.status(status).json({ error: message });
});
4. CORS Middleware
const cors = require('cors');
// Simple — allow all origins
app.use(cors());
// Production config
const corsOptions = {
origin: (origin, callback) => {
const allowed = ['https://app.example.com', 'https://admin.example.com'];
if (!origin || allowed.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400, // preflight cache 24h
};
app.use(cors(corsOptions));
// Per-route CORS override
app.get('/public', cors({ origin: '*' }), (req, res) => {
res.json({ data: 'public endpoint' });
});
5. Body-Parser & Helmet
const helmet = require('helmet');
// Helmet — sets secure HTTP headers
app.use(helmet());
// Fine-grained helmet config
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-GENERATED_NONCE'"],
styleSrc: ["'self'", 'https://fonts.googleapis.com'],
imgSrc: ["'self'", 'data:', 'https:'],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
referrerPolicy: { policy: 'same-origin' },
}));
// Body parsing (built-in since Express 4.16)
app.use(express.json({ limit: '10kb' })); // JSON bodies
app.use(express.urlencoded({ extended: true })); // form bodies
app.use(express.raw({ type: 'application/octet-stream', limit: '5mb' }));
6. Middleware Execution Order
| Order | Middleware | Purpose |
|---|---|---|
| 1 | helmet() | Security headers — run first |
| 2 | cors() | CORS headers before any logic |
| 3 | express.json() | Parse request body |
| 4 | rateLimit() | Throttle before heavy work |
| 5 | requireAuth() | Authenticate user |
| 6 | routes | Business logic |
| 7 | errorHandler | Must be last |