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.
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.
You now know how to protect your API from abuse. Next, learn environment variables, the standard way to configure apps across environments securely.