TL;DR
Rate Limiting is restricting the number of API requests in a given time. It protects against abuse, DDoS and ensures fair resource access. Here's how to implement rate limiting in 2026.
Who this is for
- Developers building public APIs
- Teams needing protection against abuse
- Companies offering API as product
- Projects requiring cost control
Keyword (SEO)
rate limiting, api rate limit, ddos protection, api security, throttling, express rate limit, redis rate limiting
What is Rate Limiting?
Rate Limiting is:
- Limiting the number of requests in time
- Protection against abuse and DDoS
- Fair access to resources
- Cost control for infrastructure
Example:
Limit: 100 requests per minute
User 1: 50 requests ✅
User 2: 150 requests ❌ (blocked after 100)
Why Rate Limiting?
1. Protection against abuse
Scenarios:
- Bot scraping data
- Brute force attacks
- API abuse
- Unauthorized access
Without rate limiting:
Attacker: 10,000 requests/second
Server: Overloaded, crash
With rate limiting:
Attacker: 10,000 requests/second
Rate limiter: Blocks after 100/min
Server: Works normally
2. Cost control
Problem:
- Each request costs (compute, database)
- Unlimited API = unlimited costs
- Difficult budget control
Solution:
- Limits per user
- Resource usage control
- Predictable costs
3. Fair access
For all users:
- One user cannot monopolize API
- Equal resource access
- Better experience
4. Error prevention
Protection against:
- Accidental infinite loop
- Client code errors
- Improper API usage
Rate Limiting strategies
1. Fixed Window
How it works:
- Time window (e.g. 1 minute)
- Counter resets each window
- Simple to implement
Example:
Window: 1 minute
Limit: 100 requests
00:00-00:59: 100 requests ✅
01:00-01:59: Reset, new 100 requests ✅
Problem:
- Burst at window boundaries
- Possible 200 requests in 2 seconds (at end and start of window)
2. Sliding Window
How it works:
- Window sliding in time
- Counts requests from last N seconds
- More fair
Example:
Limit: 100 requests per 60 seconds
00:00: 50 requests (available: 50)
00:30: 30 requests (available: 20)
01:00: 20 requests (available: 0, blocked)
01:30: Requests from 00:30 expire (available: 20)
3. Token Bucket
How it works:
- Bucket with tokens
- Tokens added at constant rate
- Request consumes token
- No tokens = blocked
Example:
Capacity: 100 tokens
Adding: 10 tokens/second
Request: 1 token
4. Leaky Bucket
How it works:
- Bucket with hole
- Requests enter bucket
- Constant leak rate
- Full bucket = blocked
Implementation
1. Express.js with express-rate-limit
Installation:
npm install express-rate-limit
Basic usage:
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: 'Too many requests, please try again later.',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', limiter);
Different limits for different endpoints:
// General limit
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
});
// Stricter limit for auth
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 login attempts
skipSuccessfulRequests: true,
});
app.use('/api/', generalLimiter);
app.use('/api/auth/login', authLimiter);
With Redis (for multiple instances):
npm install express-rate-limit redis
import RedisStore from 'rate-limit-redis';
import { createClient } from 'redis';
const redisClient = createClient();
await redisClient.connect();
const limiter = rateLimit({
store: new RedisStore({
client: redisClient,
prefix: 'rl:',
}),
windowMs: 15 * 60 * 1000,
max: 100,
});
2. Next.js API Routes
Middleware approach:
// lib/rateLimit.ts
import { NextRequest, NextResponse } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '10 s'),
});
export async function rateLimit(request: NextRequest) {
const ip = request.ip ?? '127.0.0.1';
const { success, limit, remaining } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}
return { limit, remaining };
}
Usage in API route:
// app/api/users/route.ts
import { rateLimit } from '@/lib/rateLimit';
export async function GET(request: NextRequest) {
const rateLimitResult = await rateLimit(request);
if (rateLimitResult instanceof NextResponse) {
return rateLimitResult; // Rate limit exceeded
}
// Normal logic
return NextResponse.json({ users: [] });
}
3. Custom implementation with Redis
Sliding window log:
import { createClient } from 'redis';
const redis = createClient();
await redis.connect();
async function checkRateLimit(
key: string,
limit: number,
windowSeconds: number
): Promise<{ allowed: boolean; remaining: number }> {
const now = Date.now();
const windowStart = now - windowSeconds * 1000;
// Remove old requests
await redis.zRemRangeByScore(key, 0, windowStart);
// Count current requests
const count = await redis.zCard(key);
if (count >= limit) {
return { allowed: false, remaining: 0 };
}
// Add new request
await redis.zAdd(key, {
score: now,
value: `${now}-${Math.random()}`,
});
// Set TTL
await redis.expire(key, windowSeconds);
return { allowed: true, remaining: limit - count - 1 };
}
// Usage
const result = await checkRateLimit('user:123', 100, 60);
if (!result.allowed) {
return res.status(429).json({ error: 'Rate limit exceeded' });
}
Best Practices
1. Different limits for different endpoints
Strategy:
const limits = {
public: { windowMs: 15 * 60 * 1000, max: 100 },
auth: { windowMs: 15 * 60 * 1000, max: 5 },
upload: { windowMs: 60 * 60 * 1000, max: 10 },
search: { windowMs: 60 * 1000, max: 20 },
};
2. User identification
Methods:
- IP address (for public APIs)
- API key (for authorized)
- User ID (for logged in)
- Session ID
Example:
function getIdentifier(req: Request): string {
// Priority: API key > User ID > IP
return (
req.headers.get('x-api-key') ||
req.user?.id ||
req.ip ||
'anonymous'
);
}
3. Informational headers
Standard headers:
res.setHeader('X-RateLimit-Limit', '100');
res.setHeader('X-RateLimit-Remaining', '50');
res.setHeader('X-RateLimit-Reset', resetTime);
4. Graceful degradation
Instead of completely blocking:
if (rateLimitExceeded) {
// Option 1: Queue request
await queueRequest(request);
// Option 2: Throttle (slow down)
await sleep(1000);
// Option 3: Return cached data
return getCachedResponse();
}
5. Monitoring and alerts
Track:
- Number of blocked requests
- Top users/IPs
- Abuse patterns
- Performance impact
Rate Limiting vs Throttling
| Rate Limiting | Throttling |
|---|---|
| Blocks after limit | Slows down requests |
| Hard limit | Soft limit |
| 429 status | Delays response |
| For security | For performance |
FAQ
What limits to set?
Depends on case:
- Public API: 100-1000/min
- Auth endpoints: 5-10/min
- Upload: 10-50/hour
- Search: 20-100/min
What to do when limit exceeded?
- Return 429 status
- Add Retry-After header
- Inform user
- Offer upgrade plan
Does rate limiting slow down API?
Minimally (usually <1ms). Worth using Redis for cache and fast checking.
How to test rate limiting?
// Test
for (let i = 0; i < 150; i++) {
const res = await fetch('/api/endpoint');
if (i >= 100) {
expect(res.status).toBe(429);
}
}