Skip to content

Rate Limiting in Node.js APIs

Without limits, a single client (malicious or just buggy) can overwhelm your API with requests. This lesson covers implementing rate limiting in Express to protect against abuse and brute-force attacks.

What Is Rate Limiting?

Rate limiting caps how many requests a client can make within a given time window, returning a 429 Too Many Requests response once that limit is exceeded. It's essential for protecting login endpoints from brute-force password guessing, preventing API abuse, and keeping shared resources (databases, third-party APIs) from being overwhelmed by any single client.

express-rate-limit is the most common middleware for this in the Express ecosystem, tracking request counts per client (by IP address by default) and enforcing configurable limits per route or across the whole app.

import rateLimit from 'express-rate-limit';

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5,                    // 5 attempts per window per IP
  message: { error: 'Too many login attempts, please try again later.' },
});

app.post('/login', loginLimiter, loginHandler);

Scoping a strict limiter specifically to /login (rather than the whole app) protects against brute-force attempts without unnecessarily restricting other, higher-traffic endpoints.

express-rate-limit Options

rateLimit({
  windowMs: 60_000,   // time window in milliseconds
  max: 100,            // max requests per window per client
  standardHeaders: true, // return rate limit info in headers
  legacyHeaders: false,
})
  • windowMs defines the length of the rate-limiting window.
  • max defines how many requests are allowed within that window.
  • By default, clients are identified by IP address, this can be customized for authenticated users.
  • Exceeding the limit returns 429 Too Many Requests automatically.

Rate Limiting Cheatsheet

Suggested limits for different types of endpoints.

Endpoint Type Example Limit
Login/authentication 5 attempts per 15 minutes per IP
Password reset requests 3 per hour per email/IP
General public API 100 requests per minute per IP or key
Search endpoints 20-30 requests per minute
Internal service-to-service calls Higher limits or exempted entirely

Different Limits for Different Routes

Not every endpoint carries the same risk. Authentication endpoints deserve strict limits to deter brute-force attacks, while general read-only endpoints can tolerate much higher traffic. Applying a single global limit across an entire app often ends up either too strict for normal use or too loose for sensitive routes.

const strictLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
const standardLimiter = rateLimit({ windowMs: 60 * 1000, max: 100 });

app.post('/login', strictLimiter, loginHandler);
app.post('/password-reset', strictLimiter, resetHandler);
app.use('/api', standardLimiter);

Rate Limiting Across Multiple Server Instances

The default in-memory store for express-rate-limit tracks counts per server process, which means a client's limit resets independently on each instance behind a load balancer. Production deployments with multiple instances need a shared store (like Redis) so limits are enforced consistently across the whole fleet.

Common Mistakes

  • Applying the same rate limit to every route regardless of sensitivity or expected traffic patterns.
  • Using the default in-memory store across multiple server instances, allowing limits to be trivially bypassed.
  • Setting limits so strict that legitimate users are frequently blocked during normal use.
  • Not rate-limiting authentication endpoints at all, leaving them exposed to brute-force attacks.

Key Takeaways

  • Rate limiting caps requests per client within a time window, returning 429 once exceeded.
  • express-rate-limit is the standard middleware for implementing this in Express apps.
  • Sensitive endpoints (login, password reset) deserve stricter limits than general API traffic.
  • Multi-instance deployments need a shared store (like Redis) for limits to be enforced consistently.

Pro Tip

Always apply a strict, dedicated rate limiter to authentication and password-reset endpoints specifically, even if the rest of your API uses a looser global limit. These endpoints are the most common brute-force targets and deserve the tightest restrictions.