Difficulty: Intermediate
How do you implement rate limiting in a Node.js API? Explain different algorithms and production considerations.
Rate limiting restricts the number of requests a client can make within a time window. It protects APIs from abuse, brute force attacks, and accidental overload. Without it, a single client can monopolize server resources.
Common algorithms include: fixed window (simple counter per time window), sliding window (more accurate, counts requests in a rolling window), token bucket (allows bursts), and leaky bucket (smooths traffic). Each has tradeoffs between simplicity, accuracy, and memory usage.
The express-rate-limit package provides easy in-memory rate limiting for single-server setups. For multi-server deployments, use a Redis-backed store so all instances share the same counters.
Different endpoints need different limits: authentication endpoints (strict, e.g., 5 attempts/15 min), API endpoints (moderate, e.g., 100 requests/min), and public pages (lenient, e.g., 1000 requests/min). Always return rate limit headers so clients can adapt.
const rateLimit = require('express-rate-limit');
// General API limiter
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true, // Return rate limit info in headers
legacyHeaders: false,
message: {
error: 'Too many requests, please try again later',
retryAfter: '15 minutes',
},
});
// Strict limiter for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: { error: 'Too many login attempts. Try again in 15 minutes.' },
skipSuccessfulRequests: true, // Only count failed attempts
});
// Apply limiters
app.use('/api', apiLimiter);
app.use('/api/auth/login', authLimiter);
app.use('/api/auth/register', authLimiter);
// Response headers sent:
// RateLimit-Limit: 100
// RateLimit-Remaining: 95
// RateLimit-Reset: 1705313100
Different limiters for different routes. skipSuccessfulRequests only counts failures (good for login). Headers tell clients their quota.
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 60,
standardHeaders: true,
store: new RedisStore({
sendCommand: (...args) => redis.call(...args),
prefix: 'rl:', // Redis key prefix
}),
keyGenerator: (req) => {
// Rate limit by user ID (authenticated) or IP (anonymous)
return req.user?.id?.toString() || req.ip;
},
skip: (req) => {
// Skip rate limiting for admin users
return req.user?.role === 'admin';
},
});
app.use('/api', limiter);
Redis store shares rate limit state across all server instances. keyGenerator customizes how clients are identified. skip exempts certain users.
const Redis = require('ioredis');
const redis = new Redis();
// Sliding window rate limiter using Redis sorted sets
async function slidingWindowRateLimit(key, limit, windowSec) {
const now = Date.now();
const windowStart = now - windowSec * 1000;
const multi = redis.multi();
// Remove old entries outside the window
multi.zremrangebyscore(key, 0, windowStart);
// Add current request
multi.zadd(key, now, `${now}-${Math.random()}`);
// Count requests in window
multi.zcard(key);
// Set expiry on the key
multi.expire(key, windowSec);
const results = await multi.exec();
const count = results[2][1];
return {
allowed: count <= limit,
remaining: Math.max(0, limit - count),
resetAt: new Date(now + windowSec * 1000),
};
}
// Middleware
function rateLimitMiddleware(limit, windowSec) {
return async (req, res, next) => {
const key = `ratelimit:${req.ip}`;
const result = await slidingWindowRateLimit(key, limit, windowSec);
res.set('X-RateLimit-Limit', limit);
res.set('X-RateLimit-Remaining', result.remaining);
if (!result.allowed) {
return res.status(429).json({ error: 'Rate limit exceeded' });
}
next();
};
}
Sliding window uses Redis sorted sets with timestamps as scores. This is more accurate than fixed windows and prevents burst-at-boundary attacks.
Rate Limiting, express-rate-limit, Sliding Window, Token Bucket, Redis Store