Fullstack CourseLearn by building
Back to week 3

Topic

Rate limiting & throttling

Definition

Rate limiting caps how many requests a client may make within a time window, and throttling is the enforcement that rejects or delays requests over that cap — in NestJS typically via @nestjs/throttler and a global ThrottlerGuard.

In simpler words

Rate limiting says “you get N requests per minute”; anything past that gets a 429 instead of reaching your handler.

Rate limiting is a judgment call about where a request cap protects the API and how Nest would enforce one. An in-memory per-IP guard is one approach — for example, throttling auth to roughly 10 attempts per 60 seconds — while @nestjs/throttler’s ThrottlerModule is the standard library approach for the same job.

After this you can

  • Name endpoints that need a tighter request cap and why
  • Sketch a global throttler plus a per-route override
  • Explain the 429 response and why a distributed store matters

When a request cap earns its place

Definition

Rate limiting is justified where repeated requests are cheap for a client but expensive or dangerous for the server — login and other credential checks, search and report endpoints, and anything that triggers email, SMS, or third-party calls.

In simpler words

Cap the routes an attacker or a runaway script would hammer: logins to slow brute force, expensive reads to protect the database, and anything that costs money per call.

Login is the classic case: without a cap, an attacker can try thousands of passwords per minute against POST /auth/login.

A global default (say 100 requests/minute per client) plus a much tighter override on sensitive routes covers most APIs without micromanaging every endpoint.

Global throttler + per-route override (sketch)

// app.module.ts
ThrottlerModule.forRoot([{ ttl: 60_000, limit: 100 }]),
// provide APP_GUARD: ThrottlerGuard globally

// auth.controller.ts — much tighter on login
@Throttle({ default: { ttl: 60_000, limit: 5 } })
@Post('login')
login(/* ... */) {}

100/min everywhere, 5/min on login — the sensitive route gets a stricter cap without changing the global default.

The 429 contract and what to key the limit on

Definition

A throttled request should return 429 Too Many Requests, ideally with a Retry-After hint, and the limiter must decide what identifies a client — commonly the IP, or the authenticated user id for logged-in routes — because the key determines who shares a bucket.

In simpler words

Return 429, tell the client when to try again, and pick a key (IP or user) that matches who you are actually protecting against.

AllExceptionsFilter would fold a ThrottlerException into the same ApiErrorBody shape as any other error, so clients see one consistent contract.

Keying on IP alone is blunt behind shared NATs and proxies; for authenticated routes, keying on the user id is often fairer. Trusting X-Forwarded-For requires a correctly configured trust-proxy setting.

What a throttled response looks like

HTTP/1.1 429 Too Many Requests
Retry-After: 42

{ "statusCode": 429, "error": "Too Many Requests", "message": "..." }

The client backs off using Retry-After instead of retrying immediately and making things worse.

App-level limiting is one layer, not the whole defense

Definition

Application-level throttling protects handler and database work, but it sits behind any edge that already terminates connections, so a complete strategy also considers CDN or reverse-proxy limits and pairs rate limiting with other controls rather than treating it as the sole protection.

In simpler words

Your Nest throttler guards your handlers; a proxy or CDN can shed abusive traffic before it ever reaches Node, and neither replaces authentication.

A reverse proxy or CDN can absorb volumetric floods cheaply; the app-level limiter is precise about business rules like “5 logins per minute per account”.

Rate limiting slows brute force but does not authenticate — pair it with lockouts, strong password rules, and the auth guards already in place.

Layers, not a single switch

Client -> CDN/proxy (coarse volumetric limits)
       -> Nest ThrottlerGuard (per-route, per-user rules)
       -> Auth guards (identity + role)
       -> Handler

Each layer rejects a different kind of abuse; the app limiter is the one that understands your routes.

Keep in mind

  • Throttle sensitive and expensive routes hardest — login before ordinary reads.
  • Return 429 with Retry-After and keep it in your standard error shape.
  • Use a shared store for counters once you run more than one instance.
  • Rate limiting complements auth and edge protection; it does not replace them.

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥80% (enforced by the API) to save progress.

Checking your session…

14 questions · concept 4 · syntax 3 · practical 4 · logic 3

1. What problem does rate limiting primarily address?
Concept
2. Which route most needs a tight rate limit?
Syntax
3. What HTTP status should a throttled request return?
Practical
4. What header tells a client when to retry after a 429?
Logic
5. How does the throttler enforce a global limit in Nest?
Concept
6. Why can in-memory counters undercount across instances?
Practical
7. What store fixes multi-instance rate limiting?
Syntax
8. What should an authenticated route key its limit on for fairness?
Logic
9. How do CDN/proxy limits relate to app-level throttling?
Concept
10. Does rate limiting replace authentication?
Practical
11. What does this decorator do?
Conceptintermediate
@Throttle({ default: { ttl: 60_000, limit: 5 } })
@Post('login')
12. Which values set 100 requests per minute globally?
Syntaxintermediate
ThrottlerModule.forRoot([{ ttl: ???, limit: 100 }])
13. What does @SkipThrottle() do on a route?
Practicalintermediate
@SkipThrottle()
@Get('health')
14. How should a ThrottlerException reach the client here?
Logicintermediate
// AllExceptionsFilter formats thrown exceptions
throw new ThrottlerException();

Checking your session…