What Happens When You Type a URL and Press Enter
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →One script hammering your login endpoint, or one user looping your AI feature, can take down a server or empty a budget. How rate limiting works, which algorithm to pick, and how to add it.
Every public endpoint will eventually get hit far harder than you planned. Sometimes it's an attacker trying thousands of passwords. Sometimes it's a scraper. Sometimes it's your own frontend stuck in a retry loop. And if your endpoint calls a paid API, an AI model for example, every one of those requests costs you money.
Rate limiting is the seatbelt for all of this. Here's how it works and how to add it without annoying real users.
A rate limiter tracks how many requests a client makes in a time window and rejects requests over the limit, usually with HTTP 429 Too Many Requests.
The three decisions you need to make:
If you're behind a proxy or CDN, make sure you read the real client IP from the trusted forwarded header, not the proxy's IP. Otherwise every request appears to come from the same address.
Fixed window. Count requests per calendar window, like "100 per minute starting at :00." Simple and cheap. The weakness: a burst at the end of one window and the start of the next can allow double the limit in a short span.
Sliding window. Counts requests in the last N seconds from now, smoothing out the boundary problem. A very good default for most APIs.
Token bucket. Each client has a bucket that refills at a steady rate, up to a maximum. Each request takes a token. It allows short bursts (a page load firing several requests) while enforcing a steady average. Widely used for public APIs.
Leaky bucket. Requests queue and are processed at a fixed rate. Good for smoothing traffic to a fragile downstream system.
If you're unsure, use a sliding window for simple per-endpoint limits and a token bucket where bursts are normal.
Layering works well: coarse IP limits at the edge, precise per-user limits in the app.
An in-memory counter works on one server. With several instances, or serverless functions that start fresh, each one counts separately and the limit stops meaning anything.
Use a shared store, typically Redis. Its atomic increment and expiry commands are perfect for counters. Hosted Redis with HTTP access (like Upstash) suits serverless platforms, and there are ready-made rate limiting libraries built on it.
In Express, express-rate-limit is a common starting point, with stores available for Redis:
import rateLimit from 'express-rate-limit'
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 10, // 10 attempts per window
standardHeaders: true, // send RateLimit headers
legacyHeaders: false,
})
app.post('/api/login', loginLimiter, loginHandler)
When you reject a request:
429 Too Many Requests.Retry-After header with how many seconds to wait.On the client side, handle 429 gracefully: back off, retry later with exponential backoff and some random jitter, and show a friendly message instead of a broken page.
If you can only protect a few, start here:
For cost-sensitive endpoints, add a daily or monthly quota per user on top of short-term rate limits, plus spending caps in the provider's dashboard.
There's no universal right limit. A practical approach:
Sensitive endpoints like login deserve much stricter limits than ordinary reads.
It slows attackers down; it doesn't replace authentication, input validation or proper access control. Combine it with CAPTCHA on suspicious signups, account lockout policies and monitoring for unusual spikes. The web application security checklist covers the rest.
It depends on the endpoint. Base it on real usage: set limits well above what legitimate heavy users need, be much stricter on login and costly endpoints, and adjust using logs.
429 Too Many Requests, ideally with a Retry-After header telling the client how long to wait.
Both, depending on the endpoint. Use IP limits for anonymous traffic and at the edge, and per-user or per-API-key limits for authenticated endpoints, where they're fairer and harder to evade.
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →There are dozens of HTTP status codes, but about fifteen cover nearly everything. What each one means, when to return it, and the mix-ups (401 vs 403, 301 vs 302) that confuse everyone.
Read →A, AAAA, CNAME, MX, TXT and NS records in plain English, why DNS changes seem slow, and the troubleshooting commands that tell you what is really going on.
Read →