API Rate Limiting and Throttling
Rate limiting protects APIs from abuse, ensures fair resource allocation, and maintains system stability. Learn to implement rate limiting strategies and throttling mechanisms.
Challenge
Section titled “Challenge”Implement effective rate limiting and throttling to prevent API abuse while maintaining good user experience and system stability.
Explanation
Section titled “Explanation”Rate Limiting Strategies
Section titled “Rate Limiting Strategies”Fixed Window: Count requests in fixed time intervals Sliding Window: Smooth rate limiting with overlapping windows Token Bucket: Allow bursts while maintaining average rate Leaky Bucket: Smooth output rate regardless of input
Headers and Status Codes
Section titled “Headers and Status Codes”429 Too Many Requests: Rate limit exceeded X-RateLimit-Limit: Total requests allowed X-RateLimit-Remaining: Requests remaining X-RateLimit-Reset: When limit resets
Code Examples
Section titled “Code Examples”1. Token Bucket Rate Limiter
Section titled “1. Token Bucket Rate Limiter”class TokenBucket { constructor(capacity, refillRate) { this.capacity = capacity; this.tokens = capacity; this.refillRate = refillRate; // tokens per second this.lastRefillTime = Date.now(); }
refill() { const now = Date.now(); const timePassed = (now - this.lastRefillTime) / 1000; const tokensToAdd = timePassed * this.refillRate;
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd); this.lastRefillTime = now; }
tryConsume(tokens = 1) { this.refill();
if (this.tokens >= tokens) { this.tokens -= tokens; return true; } return false; }}
module.exports = TokenBucket;2. Express Rate Limiting Middleware
Section titled “2. Express Rate Limiting Middleware”const express = require('express');const rateLimit = require('express-rate-limit');
const app = express();
// General API limiterconst limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs message: 'Too many requests from this IP', standardHeaders: true, // Return rate limit info in `RateLimit-*` headers legacyHeaders: false, // Disable `X-RateLimit-*` headers});
// Stricter limiter for auth endpointsconst authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, // 5 requests per 15 minutes skipSuccessfulRequests: true, // Don't count successful requests message: 'Too many login attempts, please try again later.',});
// Apply limitersapp.use('/api/', limiter);app.post('/api/auth/login', authLimiter, (req, res) => { res.json({ message: 'Login successful' });});
app.listen(3000);3. Redis-Based Rate Limiting
Section titled “3. Redis-Based Rate Limiting”const redis = require('redis');
class RedisRateLimiter { constructor(client) { this.client = client; }
async isAllowed(key, limit, windowSeconds) { const current = await this.client.incr(key);
if (current === 1) { await this.client.expire(key, windowSeconds); }
return { allowed: current <= limit, remaining: Math.max(0, limit - current), resetIn: await this.client.ttl(key), }; }
async getRateInfo(key) { const count = await this.client.get(key); const ttl = await this.client.ttl(key); return { count: parseInt(count) || 0, ttl }; }}
module.exports = RedisRateLimiter;4. User-Based Rate Limiting
Section titled “4. User-Based Rate Limiting”const express = require('express');const redis = require('redis');const RedisRateLimiter = require('./redisRateLimiter');
const app = express();const client = redis.createClient();const limiter = new RedisRateLimiter(client);
app.use(async (req, res, next) => { if (!req.user) { return next(); }
const userId = req.user.id; const tier = req.user.tier || 'free';
const limits = { free: { requests: 100, window: 3600 }, pro: { requests: 1000, window: 3600 }, enterprise: { requests: 10000, window: 3600 }, };
const config = limits[tier]; const key = `rate:${userId}:${Math.floor(Date.now() / (config.window * 1000))}`;
const result = await limiter.isAllowed(key, config.requests, config.window);
res.set('X-RateLimit-Limit', config.requests); res.set('X-RateLimit-Remaining', result.remaining); res.set('X-RateLimit-Reset', Math.floor(Date.now() / 1000) + result.resetIn);
if (!result.allowed) { return res.status(429).json({ error: 'Rate limit exceeded', remaining: result.remaining, resetIn: result.resetIn, }); }
next();});
app.get('/api/data', (req, res) => { res.json({ data: 'protected data' });});
app.listen(3000);5. Sliding Window Rate Limiting
Section titled “5. Sliding Window Rate Limiting”class SlidingWindowRateLimiter { constructor(client) { this.client = client; }
async isAllowed(key, limit, windowMs) { const now = Date.now(); const windowStart = now - windowMs;
// Remove old entries await this.client.zRemRangeByScore(key, '-inf', windowStart);
// Count current requests const count = await this.client.zCard(key);
if (count >= limit) { return { allowed: false, remaining: 0 }; }
// Add current request with timestamp await this.client.zAdd(key, { score: now, member: `${now}-${Math.random()}` }); await this.client.expire(key, Math.ceil(windowMs / 1000));
return { allowed: true, remaining: limit - count - 1, resetIn: (await this.client.zScore(key, this.client.getLastMember(key))) - now, }; }}
module.exports = SlidingWindowRateLimiter;6. Adaptive Rate Limiting
Section titled “6. Adaptive Rate Limiting”class AdaptiveRateLimiter { constructor() { this.baseLimit = 100; this.cpuThreshold = 80; // CPU percentage this.memoryThreshold = 85; // Memory percentage }
getSystemMetrics() { const os = require('os'); const cpus = os.cpus(); const totalMem = os.totalmem(); const freeMem = os.freemem();
return { cpuUsage: Math.random() * 100, // Simplified memoryUsage: ((totalMem - freeMem) / totalMem) * 100, }; }
calculateLimit() { const metrics = this.getSystemMetrics(); let limit = this.baseLimit;
if (metrics.cpuUsage > this.cpuThreshold) { limit *= 0.5; // Reduce by 50% } if (metrics.memoryUsage > this.memoryThreshold) { limit *= 0.7; // Reduce by 30% }
return Math.floor(limit); }
async checkLimit(key) { const currentLimit = this.calculateLimit(); // Use the dynamic limit in rate limiting logic return currentLimit; }}
module.exports = AdaptiveRateLimiter;Best Practices
Section titled “Best Practices”-
Choose Appropriate Limits
- Different limits for different endpoints
- User-tier-based limits
- Graceful degradation under load
-
Clear Communication
- Use standard HTTP headers
- Provide reset time information
- Document rate limits in API docs
-
Fair Distribution
- Prevent single user/IP from consuming all quota
- Implement per-endpoint limits
- Allow burst traffic
-
Monitor and Alert
- Track rate limit violations
- Alert on suspicious patterns
- Adjust limits based on usage
-
Handling Overages
- Return appropriate HTTP status
- Queue requests instead of rejecting
- Implement priority queues for critical users
Real-World Applications
Section titled “Real-World Applications”- Public APIs: Protect infrastructure from abuse
- SaaS Platforms: Enforce usage tiers
- Payment APIs: Prevent fraud and abuse
- Content Delivery: Fair bandwidth allocation
- Authentication: Prevent brute force attacks