security
Rate limiting, and who counts as one caller
Rate limiting, and who counts as one caller
@nestjs/throttler is NestJS's official rate-limiting module. It tracks requests per client — by IP by default — in a rolling window, and rejects past a configured limit with a 429.
ThrottlerModule.forRoot([{ ttl: 60000, limit: 10 }]);
@Throttle({ default: { limit: 3, ttl: 60000 } })
@Post("login")
login() { ... }
Login and auth endpoints need tighter limits than the rest of the API. A general endpoint might tolerate 100 requests/minute per client; a login endpoint is the target for credential stuffing. Throttling login specifically — a handful per minute per IP, tighter per account — is cheap and high-value, and it sits in front of app-level lockout logic, stopping attacker volume before it even reaches the point of checking credentials.
Two things that aren't obvious
Default in-memory storage only works correctly with a single instance. With multiple replicas behind a load balancer, each tracks its own counters, so the effective limit becomes the configured limit times the instance count, with no error anywhere. Real distributed limiting needs shared storage — a Redis-backed adapter keeps every instance counting against the same state.
IP-based limiting alone isn't enough — and can be actively harmful. I found this reasoning about whether the backend was safe to cluster: the default keyed on req.ip for every route, including authenticated ones. In production, every user on the same office WiFi and every staff member behind the same NAT would share one budget — one heavy user could silently throttle everyone else, indistinguishable from an attack. The fix keys on the authenticated user's identity when there is one, falling back to IP only for requests that haven't logged in yet. It needed a small reorder of the guard chain, so auth populates req.user before the throttler runs.
Throttling is one layer. Pair it with account lockout after N failures, CAPTCHA on repeated failures, and monitoring for unusual patterns. Rate limiting slows an attacker down; it doesn't stop a patient one.