On this page
How to Add Rate Limiting to an API
Add rate limiting to a Node.js API with the express-rate-limit middleware: per-user limits, 429 responses, and retry headers. A working example.
Quick answer
- Add rate limiting with middleware that tracks requests per client and rejects excess with 429.
express-rate-limitgives you a configurable limiter in a few lines.- Key limits on the user or API key, not just the IP, and return
Retry-After.
What you’ll build
Per-client rate limiting on an Express API: a global limit, a stricter limit on a sensitive endpoint, and correct 429 responses.
Step 1 — Add the middleware
Install and configure express-rate-limit:
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
limit: 100, // 100 requests per window
standardHeaders: true, // RateLimit-* headers
legacyHeaders: false,
});
app.use(limiter);How to verify it worked: hitting the API more than 100 times in a minute returns 429 with a Retry-After header.
Step 2 — Tighten sensitive endpoints
Apply a stricter limit where it matters — login, password reset, payment:
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 10,
standardHeaders: true,
legacyHeaders: false,
});
app.post("/login", loginLimiter, (req, res) => {
// ...
});Step 3 — Key on identity, not just IP
By default, express-rate-limit keys on IP. Behind a proxy or with authenticated users, key on the user instead:
const userLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 100,
keyGenerator: (req) => req.user?.id || req.ip,
});How to verify it worked: the limit is applied per user, so many users behind one IP aren’t collectively blocked.
Step 4 — Handle 429 gracefully
Return a clear message and the standard headers so clients know to back off. The default 429 already includes Retry-After when standardHeaders is on.
Step 5 — Distribute it
For multiple server instances, use a shared store (like Redis) so the limit is global across instances, not per-process. How to verify it worked: with two instances, a client’s count is shared, not doubled.
Where this bites vibecoders
The AI-generated API usually has authentication but no rate limiting — the two solve different problems. The highest-value first step is a simple global limit plus a strict login limit, which blocks brute force and runaway loops immediately. Add identity-based keys and a shared store only when you scale; the basics are a few lines.
Where AI coding assistants get this wrong
- Omitting rate limiting entirely from generated APIs.
- Keying on IP behind a proxy, so every user appears to come from the proxy’s IP.
- Forgetting a strict limit on login/credential endpoints.
- Not returning
Retry-After, so clients don’t know when to resume.
Checklist
- Add a global limit plus stricter limits on sensitive endpoints.
- Key on identity (user/API key) where available.
- Return 429 with
Retry-After. - Use a shared store across multiple instances.
- Log limit hits to spot abuse and misbehaving clients.
FAQ
What is express-rate-limit?
A popular Express middleware that enforces request limits per window and returns 429 when exceeded. It supports custom key generation, headers, and pluggable stores (memory or Redis).
Why does 429 matter over 500?
429 Too Many Requests tells the client “you’re over the limit, retry later” — a normal, recoverable condition. A 500 implies a server error and triggers incorrect retry behavior. Using the right status is part of the API contract.
Do I need Redis for rate limiting?
Only when you run multiple server instances. With one instance, in-memory limits are fine. Multiple instances need a shared store so a client’s count is consistent across them.