Express Middleware
Middleware functions are the backbone of Express. They have access to the request and response objects and the next() function. Middleware can execute code, modify req/res, end the request-response cycle, or call the next middleware in the stack.
Writing Custom Middleware
// Application-level middleware (runs on every request)
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
next(); // MUST call next() or the request hangs
});
// Middleware factory - returns middleware with custom options
function requestLogger(options = {}) {
const { prefix = 'LOG' } = options;
return (req, res, next) => {
console.log(`[${prefix}] ${req.method} ${req.url}`);
next();
};
}
app.use(requestLogger({ prefix: 'API' }));
// Middleware that attaches data to req for downstream handlers
app.use(async (req, res, next) => {
try {
req.requestId = crypto.randomUUID();
req.startTime = Date.now();
next();
} catch (err) {
next(err); // Pass errors to error-handling middleware
}
});
// Route-specific middleware
const requireAdmin = (req, res, next) => {
if (req.user?.role !== 'admin') {
return res.status(403).json({ error: 'Admin access required' });
}
next();
};
app.get('/admin/users', requireAdmin, (req, res) => {
res.json(allUsers);
});Error-Handling Middleware
Error-handling middleware has four parameters: (err, req, res, next). It must be defined AFTER all routes and regular middleware. Errors are passed to it by calling next(err) anywhere in the pipeline.
// Custom error class
class AppError extends Error {
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
super(message);
this.statusCode = statusCode;
this.code = code;
this.isOperational = true;
}
}
// 404 handler (place after all routes)
app.use((req, res, next) => {
next(new AppError(`Route ${req.path} not found`, 404, 'NOT_FOUND'));
});
// Global error handler (4 params - Express identifies this as error handler)
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const message = err.isOperational ? err.message : 'Something went wrong';
// Log unexpected errors
if (!err.isOperational) {
console.error('Unexpected error:', err);
}
res.status(statusCode).json({
error: {
message,
code: err.code || 'INTERNAL_ERROR',
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
},
});
});
// Async error wrapper (avoids try-catch in every route)
const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await db.findUser(req.params.id);
if (!user) throw new AppError('User not found', 404);
res.json(user);
}));Third-Party Middleware
The Express ecosystem has well-maintained middleware for every common need. These are the packages you will reach for in almost every production application.
npm install cors helmet morgan cookie-parser compression express-rate-limitconst cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
// Security headers
app.use(helmet());
// CORS configuration
app.use(cors({
origin: ['https://myapp.com', 'https://www.myapp.com'],
credentials: true, // Allow cookies/auth headers
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
// HTTP request logging
app.use(morgan('combined')); // 'dev' for development, 'combined' for production
// Cookie parsing
app.use(cookieParser(process.env.COOKIE_SECRET));
// Access cookies: req.cookies.name, req.signedCookies.name
// Gzip compression for responses
app.use(compression());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Max 100 requests per window per IP
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Too many requests, please try again later.' },
});
app.use('/api/', limiter);Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free