API Rate Limiting Implementation
Protect your API from abuse. Here's how to implement rate limiting in Node.js.
Why Rate Limit
Without limits:
- One client can overwhelm your server
- Brute force attacks succeed
- Costs spiral (if using paid services)
With limits:
- Fair resource distribution
- Protection from abuse
- Predictable performance
Simple Token Bucket
class RateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.clients = new Map();
}
isAllowed(clientId) {
const now = Date.now();
const client = this.clients.get(clientId);
if (!client || now - client.windowStart > this.windowMs) {
this.clients.set(clientId, { count: 1, windowStart: now });
return true;
}
if (client.count < this.maxRequests) {
client.count++;
return true;
}
return false;
}
}
// 100 requests per minute
const limiter = new RateLimiter(100, 60000);
Express Middleware
Using `express-rate-limit`:
npm install express-rate-limit
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: {
error: 'Too many requests, please try again later'
},
standardHeaders: true, // Return rate limit info in headers
legacyHeaders: false
});
// Apply to all API routes
app.use('/api', apiLimiter);
Per-Endpoint Limits
Different endpoints have different costs:
const strictLimiter = rateLimit({
windowMs: 60000,
max: 5 // Only 5 per minute
});
const relaxedLimiter = rateLimit({
windowMs: 60000,
max: 100
});
app.post('/api/login', strictLimiter, loginHandler);
app.get('/api/products', relaxedLimiter, productsHandler);
Redis for Distributed Systems
In-memory limiters don't work across multiple servers. Use Redis:
npm install rate-limit-redis
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redisClient = new Redis(process.env.REDIS_URL);
const limiter = rateLimit({
store: new RedisStore({
sendCommand: (...args) => redisClient.call(...args)
}),
windowMs: 15 * 60 * 1000,
max: 100
});
Response Headers
Good APIs tell clients their limit status:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1639641600
Retry-After: 60
Clients can back off gracefully.
Identifying Clients
Options:
- IP address (default, can be shared)
- API key (best for APIs)
- User ID (after authentication)
const limiter = rateLimit({
keyGenerator: (req) => {
return req.user?.id || req.ip;
},
// ...
});
What Limits to Set
| Endpoint Type | Suggestion | |--------------|------------| | Login | 5-10 per minute | | Password reset | 3-5 per hour | | General API | 60-100 per minute | | Expensive operations | 10-20 per minute | | Public endpoints | Higher, but still limited |
Start conservative. Adjust based on real usage.
Rate limiting is basic hygiene. Don't skip it.