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-limit

Add 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: 530

Client 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.