Rate Limiting and Throttling
Why It Matters
APIs need protection from accidental overload, abusive clients, credential stuffing, scraping, and expensive request patterns. Rate limiting and throttling control how much traffic a caller can send over time.
For Node.js backends, these controls help keep the event loop and shared dependencies healthy. They also make API usage fair across users and tenants.
Core Concepts
Rate Limiting
Rate limiting rejects requests that exceed a configured limit, such as 100 requests per minute per API key.
Throttling
Throttling slows requests down instead of immediately rejecting them. It can smooth bursts but may tie up resources if implemented poorly in the application process.
Identity
A limit needs an identity key. Common keys include:
- IP address.
- User ID.
- API key ID.
- Tenant ID.
- Route plus user or route plus IP.
IP-only limits are easy but imperfect because many users can share one NAT IP, and attackers can use many IPs.
Algorithms
Common algorithms include:
- Fixed window: simple counter reset every interval.
- Sliding window: smoother counting across time boundaries.
- Token bucket: allows bursts up to bucket capacity while refilling steadily.
- Leaky bucket: processes at a steady rate.
Syntax/Examples
Simple Fixed Window Middleware
export function createFixedWindowRateLimit({ limit, windowMs }) {
const counters = new Map();
return (req, res, next) => {
const key = req.user?.id ?? req.ip;
const now = Date.now();
const current = counters.get(key);
if (!current || now > current.resetAt) {
counters.set(key, { count: 1, resetAt: now + windowMs });
return next();
}
current.count += 1;
if (current.count > limit) {
res.setHeader('Retry-After', Math.ceil((current.resetAt - now) / 1000));
return res.status(429).json({ error: 'Too many requests' });
}
next();
};
}This demonstrates the idea, but production systems should use a shared store such as Redis when multiple Node instances are running.
Route-Specific Limits
app.post('/login', createFixedWindowRateLimit({ limit: 5, windowMs: 60_000 }), loginHandler);
app.get('/products', createFixedWindowRateLimit({ limit: 300, windowMs: 60_000 }), listProductsHandler);Login should usually have stricter limits than public read endpoints because it is security-sensitive.
Token Bucket Sketch
export function createTokenBucket({ capacity, refillPerSecond }) {
const buckets = new Map();
return function consume(key) {
const now = Date.now() / 1000;
const bucket = buckets.get(key) ?? { tokens: capacity, updatedAt: now };
const elapsed = now - bucket.updatedAt;
bucket.tokens = Math.min(capacity, bucket.tokens + elapsed * refillPerSecond);
bucket.updatedAt = now;
if (bucket.tokens < 1) {
buckets.set(key, bucket);
return false;
}
bucket.tokens -= 1;
buckets.set(key, bucket);
return true;
};
}Token bucket allows short bursts while preserving a long-term average.
Use Cases
Use rate limits for:
- Login and password reset endpoints.
- Public API keys.
- Expensive search endpoints.
- Webhook ingestion.
- Per-tenant fairness in SaaS apps.
Use throttling when:
- You control clients and want backpressure instead of hard failure.
- Background jobs can be slowed safely.
- Downstream APIs require controlled request rates.
Tradeoffs
Strict limits protect systems but can block legitimate users during spikes. Loose limits may not stop abuse. Good limits often vary by endpoint, authentication state, customer plan, and risk level.
Distributed rate limiting requires atomic counters. Redis operations or purpose-built gateways are common. In-memory counters are acceptable for local development and simple demos, not for horizontally scaled enforcement.
Common Mistakes
- Using only in-memory limits in a multi-instance deployment.
- Applying the same limit to every route.
- Trusting
X-Forwarded-Forwithout trusted proxy configuration. - Not returning
429andRetry-Afterfor rejected requests. - Blocking legitimate shared IPs with aggressive IP-only limits.
Practical Challenge
Add rate limiting to /login by IP and email combination. Add a separate per-user limit to an authenticated /reports/export endpoint. Explain why the identity key differs for each route.
Recap
Rate limiting rejects excess traffic. Throttling slows traffic. Both protect Node APIs and shared dependencies. Choose keys and algorithms carefully, use shared storage in scaled systems, and tune limits by route risk and cost.