Without rate limiting, a single user — or a bot — can make thousands of requests per second to your API, overwhelming your servers, degrading performance for everyone, and running up your infrastructure costs. Rate limiting protects your API by restricting how many requests a client can make in a given time window.
Why Rate Limit?
- Prevent abuse: Block brute-force login attempts, spam, and data scraping
- Protect resources: Prevent a single user from hogging server capacity
- Ensure fairness: Give all users equal access to your API
- Control costs: Limit expensive operations (AI inference, database queries)
- SLA compliance: Enforce usage tiers in your API pricing plans
Rate Limiting Algorithms
1. Fixed Window
Count requests in fixed time intervals (e.g., per minute). When the counter exceeds the limit, reject until the next window.
Window: 1 minute | Limit: 100 requests 12:00:00 - 12:00:59 → Request 1...100 ✅ | Request 101 ❌ 12:01:00 - 12:01:59 → Counter resets → Request 1...100 ✅ ⚠️ Problem: A user can send 100 requests at 12:00:59 and another 100 at 12:01:00 — 200 requests in 2 seconds!
2. Sliding Window Log
Track the timestamp of every request. Count requests in the last N seconds. Most accurate but memory-intensive.
3. Sliding Window Counter
Combines fixed window and sliding window. Uses weighted average of current and previous window counts. Good balance of accuracy and memory.
4. Token Bucket (Recommended)
A "bucket" holds tokens. Each request consumes a token. Tokens are added at a fixed rate. If the bucket is empty, requests are rejected. Allows short bursts while enforcing an average rate.
Bucket capacity: 10 tokens
Refill rate: 1 token/second
Second 0: Bucket has 10 tokens
→ User sends 5 requests → 5 tokens consumed → 5 remaining
Second 1: 1 token added → 6 tokens
→ User sends 1 request → 5 remaining
Second 5: 4 tokens added → 9 tokens
→ User sends burst of 9 → 0 remaining
Second 6: 1 token added → 1 token
→ User can make 1 request
Implementation with Redis
import Redis from 'ioredis';
const redis = new Redis();
async function rateLimiter(req, res, next) {
const key = `rate:${req.ip}`;
const limit = 100; // requests
const windowMs = 60 * 1000; // per minute
const now = Date.now();
const windowStart = now - windowMs;
// Atomic Redis operations
const pipeline = redis.pipeline();
pipeline.zremrangebyscore(key, 0, windowStart); // Remove old entries
pipeline.zadd(key, now, `${now}-${Math.random()}`); // Add current request
pipeline.zcard(key); // Count requests in window
pipeline.expire(key, 60); // Auto-cleanup
const results = await pipeline.exec();
const requestCount = results[2][1];
// Set rate limit headers
res.set({
'X-RateLimit-Limit': limit,
'X-RateLimit-Remaining': Math.max(0, limit - requestCount),
'X-RateLimit-Reset': Math.ceil((now + windowMs) / 1000),
});
if (requestCount > limit) {
res.set('Retry-After', '60');
return res.status(429).json({
error: 'Too Many Requests',
message: 'Rate limit exceeded. Please try again later.',
retryAfter: 60,
});
}
next();
}
app.use('/api/', rateLimiter);
Rate Limit Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 100 // Max requests per window
X-RateLimit-Remaining: 73 // Requests remaining
X-RateLimit-Reset: 1720396800 // Unix timestamp when window resets
HTTP/1.1 429 Too Many Requests
Retry-After: 60 // Seconds until client can retry
Content-Type: application/json
{
"error": "Too Many Requests",
"retryAfter": 60
}
Best Practices
- Rate limit by API key for authenticated users, by IP for anonymous users
- Use different limits for different endpoints — login endpoints need stricter limits than read-only endpoints
- Return informative 429 responses with
Retry-Afterheader - Include rate limit headers on every response so clients can self-regulate
- Use Redis for distributed rate limiting across multiple server instances
- Consider tiered limits — free users get 100/min, paid users get 1000/min
- Don't rate limit health check endpoints — monitoring services need unrestricted access
Try Our Free Developer Tools
Format and validate your API responses instantly.