Rate Limiting

Cap Abuse, Protect Real Users

Rate Limiting

express-rate-limit caps how many requests a client can make. Lock down login endpoints especially.

3 min read Level 2/5 #express#rate-limit#security
What you'll learn
  • Apply a global rate limit
  • Tighter limits on auth endpoints
  • Use Redis for multi-instance setups

Rate limiting is the cheapest defense against abuse. Apply it before expensive operations (DB lookups, password hashing, AI calls).

Install

npm install express-rate-limit

Global Limit

import rateLimit from "express-rate-limit";

const apiLimiter = rateLimit({
  windowMs: 60 * 1000,   // 1 min
  max:      100,         // 100 req/min per IP
  standardHeaders: true,
  legacyHeaders:   false,
});

app.use(apiLimiter);

Clients over the limit → automatic 429 response with helpful headers (RateLimit-Limit, RateLimit-Remaining, Retry-After).

Stricter on Auth

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,   // 15 min
  max:      5,                 // 5 attempts
  message:  "Too many login attempts. Try again in 15 minutes.",
});

app.post("/auth/login",    authLimiter, loginHandler);
app.post("/auth/reset",    authLimiter, resetHandler);

5 attempts per 15 minutes per IP. Brute force becomes impractical.

Per-User vs Per-IP

By default, the limiter keys by IP. For per-user limits:

const userLimiter = rateLimit({
  windowMs: 60_000,
  max: 1000,
  keyGenerator: (req) => req.user?.id ?? req.ip,
});

Authenticated users get a higher limit; anonymous users fall back to IP-based.

Multiple Instances → Redis

When you run multiple Node processes (cluster, multiple containers), in-memory counters are wrong — each process has its own.

npm install rate-limit-redis ioredis
import { RedisStore } from "rate-limit-redis";
import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL);

const apiLimiter = rateLimit({
  store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
  windowMs: 60_000,
  max: 100,
});

Now all instances share the same counter.

trust proxy

If you’re behind a proxy (Nginx, Cloudflare), Express sees the proxy’s IP as req.ip — every request looks like the same client. Bad for rate limits.

app.set("trust proxy", 1);

Tells Express to honor X-Forwarded-For. Only do this when you really are behind a known proxy — otherwise clients can spoof their IP.

Beyond Express

For sophisticated abuse, the application layer is too late.

  • Cloudflare — bot detection, rate limit at the edge
  • AWS WAF — managed rule sets
  • Fastly Edge — programmable edge rate limits

But express-rate-limit is the baseline. Add it on day one.

Input Sanitization →