Table Of Contents
Problem
You need to protect your Express.js application from abuse by limiting the number of requests users can make within a specific time window to prevent spam, brute force attacks, and server overload.
Solution
const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();
// 1. Basic Rate Limiting
const basicLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: {
error: 'Too many requests',
message: 'You have exceeded the rate limit. Try again later.',
retryAfter: '15 minutes'
},
standardHeaders: true, // Return rate limit info in headers
legacyHeaders: false, // Disable X-RateLimit-* headers
});
// Apply to all routes
app.use(basicLimiter);
// 2. Strict Rate Limiting for Authentication
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Only 5 login attempts per IP
message: {
error: 'Too many login attempts',
message: 'Account temporarily locked. Try again in 15 minutes.'
},
skipSuccessfulRequests: true, // Don't count successful logins
});
app.post('/auth/login', authLimiter, (req, res) => {
// Simulate login logic
const { email, password } = req.body;
if (email === 'user@example.com' && password === 'password') {
res.json({ success: true, token: 'jwt-token-here' });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
// 3. API Key Based Rate Limiting
const createApiLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: (req) => {
// Different limits based on API key tier
const apiKey = req.headers['x-api-key'];
if (apiKey === 'premium-key') return 10000;
if (apiKey === 'standard-key') return 1000;
return 100; // Free tier
},
keyGenerator: (req) => {
// Use API key instead of IP for rate limiting
return req.headers['x-api-key'] || req.ip;
},
message: (req) => ({
error: 'API rate limit exceeded',
limit: typeof req.rateLimit?.max === 'function'
? req.rateLimit.max(req)
: req.rateLimit?.max,
remaining: req.rateLimit?.remaining,
resetTime: req.rateLimit?.resetTime
})
});
app.get('/api/data', createApiLimiter, (req, res) => {
res.json({
data: 'Your API data here',
rateLimit: {
limit: req.rateLimit.limit,
remaining: req.rateLimit.remaining,
reset: new Date(req.rateLimit.resetTime)
}
});
});
// 4. Different Limits for Different Routes
const uploadLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 3, // Only 3 uploads per minute
message: 'Upload limit exceeded. Please wait before uploading again.'
});
const searchLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 30, // 30 searches per minute
message: 'Search limit exceeded. Please slow down.'
});
app.post('/upload', uploadLimiter, (req, res) => {
res.json({ message: 'File uploaded successfully' });
});
app.get('/search', searchLimiter, (req, res) => {
const query = req.query.q;
res.json({
query,
results: [`Result 1 for ${query}`, `Result 2 for ${query}`]
});
});
// 5. Custom Store (using Redis for distributed systems)
// Uncomment if using Redis
/*
const RedisStore = require('rate-limit-redis');
const redis = require('redis');
const redisClient = redis.createClient();
const distributedLimiter = rateLimit({
store: new RedisStore({
sendCommand: (...args) => redisClient.sendCommand(args),
}),
windowMs: 15 * 60 * 1000,
max: 100
});
*/
// 6. Skip Certain Requests
const smartLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
skip: (req) => {
// Skip rate limiting for trusted IPs
const trustedIPs = ['127.0.0.1', '::1'];
return trustedIPs.includes(req.ip);
},
skipFailedRequests: true, // Don't count failed requests
});
app.use('/api', smartLimiter);
// 7. Dynamic Rate Limiting
const dynamicLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: (req) => {
// Higher limits during off-peak hours
const hour = new Date().getHours();
if (hour >= 2 && hour <= 6) {
return 200; // Off-peak hours
}
return 50; // Peak hours
},
message: (req) => {
const hour = new Date().getHours();
const isPeakHour = !(hour >= 2 && hour <= 6);
return {
error: 'Rate limit exceeded',
message: isPeakHour
? 'Peak hour limits apply (50 req/min)'
: 'Off-peak limits apply (200 req/min)'
};
}
});
// 8. Rate Limit Headers and Monitoring
app.use((req, res, next) => {
// Add custom rate limit headers
if (req.rateLimit) {
res.set({
'X-RateLimit-Limit': req.rateLimit.limit,
'X-RateLimit-Remaining': req.rateLimit.remaining,
'X-RateLimit-Reset': new Date(req.rateLimit.resetTime).toISOString()
});
}
next();
});
app.get('/status', (req, res) => {
res.json({
message: 'Server is running',
timestamp: new Date().toISOString(),
rateLimit: req.rateLimit ? {
limit: req.rateLimit.limit,
remaining: req.rateLimit.remaining,
resetTime: new Date(req.rateLimit.resetTime).toISOString()
} : 'No rate limit applied'
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Install express-rate-limit:
npm install express-rate-limit
# For Redis store (optional)
npm install rate-limit-redis redis
Test rate limiting:
# Test basic rate limiting
for i in {1..10}; do curl http://localhost:3000/status; echo; done
# Test auth rate limiting
for i in {1..6}; do
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"wrong","password":"wrong"}'
echo
done
Explanation
express-rate-limit
tracks requests per IP address within a time window. Configure windowMs
for the time period and max
for request limits. The middleware automatically returns 429 (Too Many Requests) when limits are exceeded.
Use different limiters for different routes based on sensitivity - stricter limits for authentication and looser limits for public APIs. For distributed systems, use Redis store to share rate limit data across multiple server instances.
Share this article
Add Comment
No comments yet. Be the first to comment!