NestJS Rate Limiting

Rate limiting caps the number of requests a client can make to your API within a time window. Without it, a single bad actor can flood your server with thousands of requests per second — crashing it, exhausting your database connections, or running up your cloud bill. Rate limiting protects your infrastructure, ensures fair usage across all clients, and is a standard security requirement for any public-facing API.

How Rate Limiting Works

Client sends requests to GET /products:

  Request 1 → allowed (1/10 used)
  Request 2 → allowed (2/10 used)
  ...
  Request 10 → allowed (10/10 used)
  Request 11 → BLOCKED (429 Too Many Requests)

  "You've made 10 requests in 60 seconds. Try again in 45 seconds."

  After 60 seconds: counter resets → 10 more requests allowed

Installing the Rate Limiter

npm install @nestjs/throttler

Global Rate Limiting Setup

// app.module.ts
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';

@Module({
  imports: [
    ThrottlerModule.forRoot([
      {
        name: 'short',
        ttl: 1000,      // 1 second window
        limit: 3,       // 3 requests per second
      },
      {
        name: 'long',
        ttl: 60000,     // 60 second window
        limit: 100,     // 100 requests per minute
      },
    ]),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,  // applies to every route globally
    },
  ],
})
export class AppModule {}

Multiple Throttle Tiers

Time Window  | Limit | Purpose
-------------|-------|--------------------------------
1 second     | 3     | Prevent rapid-fire burst requests
60 seconds   | 100   | Enforce overall usage cap per minute
1 hour       | 1000  | Daily API quota enforcement

Client blocked when any limit is hit first.

Skipping Rate Limiting on Specific Routes

import { SkipThrottle } from '@nestjs/throttler';

@Controller('health')
@SkipThrottle()    // health checks must never be rate-limited
export class HealthController {
  @Get()
  check() { return 'OK'; }
}

Custom Limits Per Route

import { Throttle } from '@nestjs/throttler';

@Controller('auth')
export class AuthController {

  @Post('login')
  @Throttle({ short: { ttl: 60000, limit: 5 } })   // 5 login attempts per minute
  login(@Body() dto: LoginDto) {
    return this.authService.login(dto);
  }

  @Post('register')
  @Throttle({ short: { ttl: 3600000, limit: 3 } })  // 3 registrations per hour
  register(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }
}

Authentication endpoints need tighter limits than regular API endpoints. An attacker trying to brute-force passwords sends thousands of login attempts per minute. A limit of 5 per minute makes brute force attacks impractical while not inconveniencing legitimate users who typically log in once.

Rate Limit Response Headers

The throttler automatically adds helpful headers to every response:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1705000060
Retry-After: 45   (only when blocked)

Clients read these headers to understand their current usage and when to retry blocked requests. Well-designed API clients back off automatically when they receive a 429 response and use Retry-After to know when to resume.

Distributed Rate Limiting with Redis

The default throttler stores counters in memory. This works fine for a single server, but fails when you run multiple application instances — each instance has its own counter, so a client can exceed the limit by spreading requests across instances. Use Redis as a shared store:

npm install @nestjs/throttler-storage-redis ioredis

ThrottlerModule.forRoot({
  throttlers: [{ ttl: 60000, limit: 100 }],
  storage: new ThrottlerStorageRedisService(
    new Redis({ host: 'localhost', port: 6379 })
  ),
}),

IP-Based vs User-Based Throttling

Default (IP-based):
  Counter keyed by client IP address
  Problem: multiple users behind NAT share one IP

Custom (user-based for authenticated routes):
  Counter keyed by user ID from JWT
  More precise — each user gets their own quota

// Custom throttler that uses user ID when authenticated
@Injectable()
export class UserThrottlerGuard extends ThrottlerGuard {
  protected async getTracker(req: Request): Promise<string> {
    return req['user']?.id?.toString() || req.ip;
  }
}

Rate limiting is one of the fastest security improvements you can make to a NestJS API. The throttler package requires minimal configuration, applies globally in two lines of code, and immediately protects every route from abuse — while remaining flexible enough to apply different limits to different endpoint types based on their sensitivity and expected usage patterns.

Leave a Comment

Your email address will not be published. Required fields are marked *