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

WindowUse case
1 sBurst protection
1 minAPI spam
15 minGeneral API
1 hPer-account quotas
1 dayFree-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 });
  }
});

7. Setting Rate Limit Headers

HeaderMeaning
RateLimit-LimitWindow allowance
RateLimit-RemainingRequests remaining in window
RateLimit-ResetSeconds until reset
Retry-AfterSent 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" });
  }
});
AlgorithmTrade-off
Fixed windowSimple; burst at boundary
Sliding windowSmoother; more state
Token bucketBurst-friendly; great for APIs
Leaky bucketSmooths 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)
});