Node.js Rate Limiting
Rate limiting caps how many requests one visitor can send in a given time. The cap protects your server from overload, password guessing, and automated abuse. Fair users keep working while heavy abusers slow down.
The Ticket Counter Idea
Think of a ticket counter that serves each person at most five tickets per hour. A person who asks for a sixth ticket waits until the hour resets. The counter stays available for everyone else.
Visitor request
|
v
+---------------+ under limit +--------------+
| Rate Limiter | -------------------> | Your route |
| counts calls | +--------------+
+---------------+
| over limit
v
429 Too Many Requests
Why Rate Limiting Matters
- It slows password guessing on login pages.
- It stops one visitor from using all server power.
- It lowers hosting costs on paid APIs.
- It keeps your app responsive during traffic spikes.
Install express-rate-limit
npm install express-rate-limitAdd a Global Limit
const express = require("express");
const rateLimit = require("express-rate-limit");
const app = express();
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 100, // 100 requests per window
standardHeaders: true,
legacyHeaders: false,
message: { error: "Too many requests. Try again later." }
});
app.use(limiter);Each visitor, identified by IP address, may send 100 requests every 15 minutes. The 101st request receives status 429.
Understand the Settings
- windowMs sets the length of the counting period in milliseconds.
- limit sets the maximum requests inside one period.
- standardHeaders sends the remaining count to the client.
- message sets the reply for blocked requests.
Add a Strict Limit for Login
Sensitive routes deserve tighter rules.
const loginLimiter = rateLimit({
windowMs: 10 * 60 * 1000,
limit: 5,
message: { error: "Too many login attempts. Wait 10 minutes." }
});
app.post("/login", loginLimiter, (req, res) => {
// check credentials here
res.send("Login route");
});
Time window: 10 minutes Attempt 1 OK Attempt 2 OK Attempt 3 OK Attempt 4 OK Attempt 5 OK Attempt 6 BLOCKED (429) Window resets --> attempts allowed again
Read the Response Headers
Standard headers tell clients their remaining allowance.
RateLimit-Limit: 100
RateLimit-Remaining: 42
RateLimit-Reset: 530Client developers use these values to slow down before they hit the cap.
Work Behind a Proxy
Hosting services often place a proxy in front of your app. Every request then appears to come from the proxy address, and all visitors share one counter. Tell Express to trust the proxy.
app.set("trust proxy", 1);Set the number to match the count of proxies between the visitor and your app.
Store Counters in Redis
The default storage lives in the memory of one process. A cluster with several processes keeps separate counters, so visitors gain extra attempts. A shared store such as Redis keeps one counter for all processes.
Memory store: Shared store: Process A: counter 3 Process A \ Process B: counter 2 Process B --> Redis: counter 5 (total 5, but nobody knows) Process C /
Limit by User Instead of IP
Offices and schools share one IP address. Many people behind it can trigger the limit together. A custom key function counts by account for logged-in users.
const userLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 30,
keyGenerator: (req) => req.user ? req.user.id : req.ip
});Slow Down Instead of Block
The express-slow-down package adds delay to each extra request. Visitors keep access while abusers lose speed.
Good Practices
- Choose limits that match real usage. Watch your traffic before you pick numbers.
- Give friendly error messages with a retry time.
- Apply stricter limits to login, sign-up, and password reset routes.
- Combine rate limits with strong passwords and monitoring.
Practice Tasks
- Set a limit of 3 requests per minute on one route and test it with repeated browser refreshes.
- Read the rate limit headers in the browser developer tools.
- Create a separate strict limiter for a login route.
