Implementing Rate Limiting
1. Installing express-rate-limit
Example: Install
npm i express-rate-limit rate-limit-redis ioredis
2. Creating Rate Limiter
Example: Basic limiter
import rateLimit from "express-rate-limit";
const limiter = rateLimit({
windowMs: 15 * 60_000, // 15 min
limit: 100, // per IP per window
standardHeaders: "draft-7", // RateLimit-* headers (RFC 9239)
legacyHeaders: false
});
app.use(limiter);
3. Setting Window Duration
| Window | Use case |
| 1 s | Burst protection |
| 1 min | API spam |
| 15 min | General API |
| 1 h | Per-account quotas |
| 1 day | Free-tier daily quota |
4. Setting Max Requests
Example: Per-tier
const tierLimits = { free: 60, pro: 600, enterprise: 6000 };
const dynamicLimiter = rateLimit({
windowMs: 60_000,
limit: (req) => tierLimits[req.user?.plan || "free"]
});
5. Customizing Error Messages
Example: JSON message
rateLimit({
windowMs: 60_000, limit: 30,
message: { error: { code: "RATE_LIMITED", message: "Slow down — try again in a minute." } }
});
6. Using Custom Handler
Example: Custom handler with Retry-After
rateLimit({
windowMs: 60_000, limit: 30,
handler: (req, res, next, options) => {
res.set("Retry-After", Math.ceil(options.windowMs / 1000));
res.status(429).json({ error: "Too Many Requests", retryAfter: options.windowMs });
}
});
| Header | Meaning |
| RateLimit-Limit | Window allowance |
| RateLimit-Remaining | Requests remaining in window |
| RateLimit-Reset | Seconds until reset |
| Retry-After | Sent on 429 |
8. Implementing Per-Route Limits
Example: Strict login limiter
const loginLimiter = rateLimit({
windowMs: 15 * 60_000, limit: 5,
skipSuccessfulRequests: true,
keyGenerator: (req) => `${req.ip}:${req.body?.email || ""}`
});
app.post("/login", loginLimiter, loginHandler);
9. Using Redis Store
Example: Distributed limiter
import { RedisStore } from "rate-limit-redis";
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
app.use(rateLimit({
windowMs: 60_000, limit: 1000,
store: new RedisStore({ sendCommand: (...args) => redis.call(...args) })
}));
Note: A shared store is required when running multiple Express instances behind a load balancer.
10. Implementing Sliding Window
Example: rate-limiter-flexible
import { RateLimiterRedis } from "rate-limiter-flexible";
const limiter = new RateLimiterRedis({
storeClient: redis, points: 100, duration: 60, blockDuration: 60
});
app.use(async (req, res, next) => {
try { await limiter.consume(req.ip); next(); }
catch (rej) {
res.set("Retry-After", Math.ceil(rej.msBeforeNext / 1000));
res.status(429).json({ error: "Too Many Requests" });
}
});
| Algorithm | Trade-off |
| Fixed window | Simple; burst at boundary |
| Sliding window | Smoother; more state |
| Token bucket | Burst-friendly; great for APIs |
| Leaky bucket | Smooths bursts to constant rate |
11. Skipping Certain Requests
Example: Skip whitelisted IPs / health
rateLimit({
windowMs: 60_000, limit: 100,
skip: (req) => req.path === "/health" || TRUSTED_IPS.has(req.ip)
});