Introduction
APIs are vulnerable to abuse—whether malicious attacks or accidental overuse. Rate limiting controls how many requests a client can make in a given time window, protecting your server from being overwhelmed and ensuring fair resource distribution.
Key Concepts
- Rate Limiting: Restricting the number of requests per time window
- Throttling: Another term for rate limiting
- TTL (Time To Live): The duration of the rate limit window
- Limit: Maximum requests allowed within the TTL
Real World Context
Rate limiting protects against:
- Brute-force attacks: Password guessing, API key enumeration
- DDoS: Distributed denial-of-service attacks
- Scraping: Automated data extraction
- Cost overruns: Limiting expensive operations
Deep Dive
Installation & Basic Setup
Install the official NestJS throttler package.
bashnpm install @nestjs/throttler
Configure the module with a throttler definition and register the guard globally using APP_GUARD.
typescriptimport { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; import { APP_GUARD } from '@nestjs/core'; @Module({ imports: [ ThrottlerModule.forRoot({ throttlers: [{ ttl: 60000, // 60 seconds limit: 10, // 10 requests per TTL }], }), ], providers: [ { provide: APP_GUARD, useClass: ThrottlerGuard, }, ], }) export class AppModule {}
Registering ThrottlerGuard as APP_GUARD applies rate limiting to every route automatically — no need to add @UseGuards() individually.
Multiple Rate Limit Tiers
Define multiple named throttlers to enforce different rate limits simultaneously — a request must pass all tiers to proceed.
typescriptThrottlerModule.forRoot([ { name: 'short', ttl: 1000, // 1 second limit: 3, // 3 requests per second }, { name: 'medium', ttl: 10000, // 10 seconds limit: 20, // 20 requests per 10 seconds }, { name: 'long', ttl: 60000, // 1 minute limit: 100, // 100 requests per minute }, ])
This layered approach catches both burst abuse (short tier) and sustained abuse (long tier), providing more nuanced protection.
Skip Rate Limiting
Exempt specific routes or entire controllers from rate limiting using the @SkipThrottle() decorator — useful for health checks and public endpoints.
typescriptimport { SkipThrottle } from '@nestjs/throttler'; @Controller('health') export class HealthController { @SkipThrottle() @Get() check() { return { status: 'ok' }; } } // Or skip entire controller @SkipThrottle() @Controller('public') export class PublicController {}
Applying @SkipThrottle() at the controller level exempts all routes within that controller.
Custom Throttle per Route
Override the global rate limit on sensitive routes using the @Throttle() decorator to apply stricter limits.
typescriptimport { Throttle } from '@nestjs/throttler'; @Controller('auth') export class AuthController { @Throttle({ default: { limit: 3, ttl: 60000 } }) @Post('login') login() { // Only 3 login attempts per minute } }
Login endpoints are prime targets for brute-force attacks, so applying a much tighter limit (3 per minute vs. the global 10) is a critical hardening step.
Custom Storage (Redis)
For distributed applications running multiple server instances, use Redis-backed storage so rate limits are shared across all nodes.
typescriptimport { ThrottlerStorageRedisService } from '@nestjs/throttler-storage-redis'; import Redis from 'ioredis'; ThrottlerModule.forRoot({ throttlers: [ { name: 'short', ttl: 1000, limit: 3 }, { name: 'long', ttl: 60000, limit: 100 }, ], storage: new ThrottlerStorageRedisService(new Redis()), })
Without Redis, each server instance tracks limits independently — an attacker could multiply their allowed requests by the number of instances behind the load balancer.
Common Pitfalls
- Not using distributed storage: In-memory storage doesn't work across multiple server instances. Use Redis for production.
- Rate limiting by IP only: Consider user ID or API key for authenticated endpoints.
- Too restrictive limits: Start generous and tighten based on actual usage data.
Best Practices
- Use Redis or similar for distributed rate limiting
- Implement different limits for different endpoint types
- Return
Retry-Afterheader when rate limited - Log rate limit hits for security monitoring
- Consider user-based limits for authenticated routes
Summary
- Use
@nestjs/throttlerto configure rate limiting with TTL (time window) and limit (max requests) - Apply globally via
APP_GUARDwithThrottlerGuard, or customize per-route with@Throttle() - Skip rate limiting on specific routes or controllers using
@SkipThrottle() - Use Redis-backed storage for distributed/multi-instance production deployments
Code Examples
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
@Module({
imports: [
ThrottlerModule.forRoot({
throttlers: [{
ttl: 60000, // 60 seconds
limit: 10, // 10 requests per TTL
}],
}),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}