Middleware
Understand and create middleware for request processing, authentication, and more
What is Middleware?
Middleware functions are functions that have access to the request object (req), response object (res), and the next middleware function in the application's request-response cycle. They can execute code, modify request/response, end the request-response cycle, or call the next middleware.
🔗 Middleware Flow
Request → Middleware 1 → Middleware 2 → Route Handler → Response
↓ ↓
next() next()
// Each middleware must either:
// 1. Call next() to pass to next middleware
// 2. Send a response to end the cycle
Basic Middleware
import express, { Request, Response, NextFunction } from 'express';
const app = express();
// Simple logging middleware
const logger = (req: Request, res: Response, next: NextFunction) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next(); // Pass to next middleware
};
// Apply globally
app.use(logger);
// Apply to specific path
app.use('/api', logger);
// Apply to specific route
app.get('/users', logger, (req, res) => {
res.json([]);
});
// Multiple middleware on a route
app.get('/protected',
authenticate,
authorize('admin'),
(req, res) => {
res.json({ secret: 'data' });
}
);
Built-in Middleware
import express from 'express';
const app = express();
// Parse JSON bodies
app.use(express.json());
// Configurable
app.use(express.json({ limit: '10mb' }));
// Parse URL-encoded bodies (form data)
app.use(express.urlencoded({ extended: true }));
// Serve static files
app.use(express.static('public'));
// With virtual path prefix
app.use('/static', express.static('public'));
// Multiple directories
app.use(express.static('public'));
app.use(express.static('uploads'));
// Raw body (for webhooks)
app.use('/webhooks', express.raw({ type: 'application/json' }));
// Text body
app.use(express.text());
Popular Third-Party Middleware
// Install
npm install cors helmet morgan compression cookie-parser express-rate-limit
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import compression from 'compression';
import cookieParser from 'cookie-parser';
import rateLimit from 'express-rate-limit';
const app = express();
// Security headers
app.use(helmet());
// CORS - Cross-Origin Resource Sharing
app.use(cors());
// Configured CORS
app.use(cors({
origin: ['http://localhost:3000', 'https://myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
}));
// HTTP request logging
app.use(morgan('dev')); // Colored dev format
app.use(morgan('combined')); // Apache combined format
// Gzip compression
app.use(compression());
// Parse cookies
app.use(cookieParser());
app.use(cookieParser('secret')); // Signed cookies
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per window
message: { error: 'Too many requests' }
});
app.use('/api', limiter);
Custom Middleware Examples
// Request timing middleware
const timing = (req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} - ${duration}ms`);
});
next();
};
// Request ID middleware
import { v4 as uuidv4 } from 'uuid';
const requestId = (req: Request, res: Response, next: NextFunction) => {
req.id = req.headers['x-request-id'] as string || uuidv4();
res.setHeader('X-Request-Id', req.id);
next();
};
// Async middleware wrapper
const asyncHandler = (fn: Function) => {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
// Usage
app.get('/users', asyncHandler(async (req, res) => {
const users = await User.findAll(); // No try/catch needed
res.json(users);
}));
// Validation middleware
const validateBody = (schema: any) => {
return (req: Request, res: Response, next: NextFunction) => {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
next();
};
};
Authentication Middleware
import jwt from 'jsonwebtoken';
interface AuthRequest extends Request {
user?: { id: string; email: string; role: string };
}
// JWT Authentication
const authenticate = (req: AuthRequest, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
req.user = decoded as AuthRequest['user'];
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
// Authorization middleware factory
const authorize = (...roles: string[]) => {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
// Usage
app.get('/admin/dashboard',
authenticate,
authorize('admin', 'superadmin'),
(req, res) => {
res.json({ message: 'Welcome, admin!' });
}
);
Middleware Order Matters!
const app = express();
// ✅ CORRECT ORDER
// 1. Security first
app.use(helmet());
app.use(cors());
// 2. Request parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// 3. Logging
app.use(morgan('dev'));
// 4. Compression
app.use(compression());
// 5. Rate limiting
app.use('/api', limiter);
// 6. Static files (if needed)
app.use(express.static('public'));
// 7. Routes
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);
// 8. 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Not found' });
});
// 9. Error handler (MUST BE LAST)
app.use(errorHandler);
💡 Middleware Best Practices
- • Always call
next()unless you're ending the response - • Keep middleware focused - one responsibility per middleware
- • Order matters - security, parsing, logging, then routes
- • Use
app.use()for global, router.use() for scoped - • Wrap async middleware to catch errors properly
- • Error-handling middleware has 4 parameters: (err, req, res, next)