RateLimitGuard
Use RateLimitGuard for fine-grained control.
The guard is safe to bind more than once
The @RateLimit() decorator already binds RateLimitGuard for you (it applies UseGuards(RateLimitGuard) internally). Binding it again — @RateLimit() on both the class and the method, or @RateLimit() together with a global { provide: APP_GUARD, useClass: RateLimitGuard } — is safe: the guard consumes the limit at most once per request, so a points: 10 limit stays 10 regardless of how many times the guard is bound on the route.
A globally-registered guard rate-limits EVERY endpoint by IP
With APP_GUARD, the guard runs on all routes, including those with no @RateLimit() metadata. A route without metadata receives empty options, is not skipped, and is limited using the default key extractor (ip) and the default points/duration from plugin options. This means health checks, static assets, SSR routes, and every other endpoint are rate-limited by client IP. To exempt routes, use the skip option (see Skipping Rate Limits).
Manual Guard Application
import { Controller, Get, UseGuards } from '@nestjs/common';
import { RateLimitGuard } from '@nestjs-redisx/rate-limit';
@Controller('api')
@UseGuards(RateLimitGuard)
export class ApiController {
@Get('data')
getData() {
return { data: 'value' };
}
}Guard Ordering
Guards execute in the order they are specified. Rate limit should typically run AFTER authentication:
import { AuthGuard } from '@nestjs/passport';
import { RateLimitGuard } from '@nestjs-redisx/rate-limit';
@Controller('api')
@UseGuards(AuthGuard('jwt'), RateLimitGuard)
@RateLimit({ key: 'user', points: 100, duration: 60 })
export class ApiController {
@Get('protected')
getProtectedData() {
// Rate limit uses request.user.id set by AuthGuard
return { protected: true };
}
}Execution Order
Global Guard
Apply to all endpoints via module (required for DI):
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { RedisModule } from '@nestjs-redisx/core';
import { RateLimitPlugin, RateLimitGuard } from '@nestjs-redisx/rate-limit';
@Module({
imports: [
RedisModule.forRoot({
clients: { host: 'localhost', port: 6379 },
plugins: [
new RateLimitPlugin({
defaultPoints: 100,
defaultDuration: 60,
}),
],
}),
],
providers: [
{
provide: APP_GUARD,
useClass: RateLimitGuard,
},
],
})
export class AppModule {}WARNING
Do not use app.useGlobalGuards(new RateLimitGuard()) — the guard requires DI (RateLimitService, Reflector, module options). Use APP_GUARD with useClass instead.
Do NOT combine global registration with @RateLimit()
When the guard is registered globally, do not also add @RateLimit() to routes — the decorator binds the guard a second time and the limit is consumed twice. With a global guard, configure per-route limits is not safe via @RateLimit(); instead rely on the default plugin-level points/duration and use skip to exempt routes. If you need different limits per route, prefer the per-route approach (no APP_GUARD, @RateLimit() only).
Also remember: a global guard limits every endpoint by IP by default, including health/static/SSR routes.
Skipping Rate Limits
Without Global Guard
When using @RateLimit per-endpoint, simply don't apply the decorator to endpoints that should be unlimited:
@Controller('api')
export class ApiController {
@Get('limited')
@RateLimit({ points: 10, duration: 60 })
limited() {
return { limited: true };
}
@Get('unlimited')
// No @RateLimit — no guard attached, no rate limiting
unlimited() {
return { unlimited: true };
}
}With Global Guard
When using APP_GUARD, the guard runs on all endpoints. Use the skip option to bypass specific routes:
@Controller('api')
export class ApiController {
@Get('limited')
@RateLimit({ points: 10, duration: 60 })
limited() {
return { limited: true };
}
@Get('unlimited')
@RateLimit({ skip: () => true })
unlimited() {
return { unlimited: true };
}
}Skip by Condition
Use skip for conditional bypassing (e.g., admin users):
@Get('data')
@RateLimit({
points: 10,
duration: 60,
skip: (ctx) => {
const req = ctx.switchToHttp().getRequest();
return req.user?.role === 'admin';
},
})
getData() {
return { data: 'value' };
}Guard Context
Access execution context within guard:
import { Injectable, ExecutionContext, CanActivate } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class CustomRateLimitGuard implements CanActivate {
constructor(private reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
// Skip for admins
if (request.user?.role === 'admin') {
return true;
}
// Custom rate limit logic
const key = `user:${request.user?.id || request.ip}`;
// Check rate limit...
return true;
}
}GraphQL Support
Rate limit GraphQL resolvers (works because GraphQL over HTTP uses HTTP context):
import { Resolver, Query, UseGuards } from '@nestjs/graphql';
import { RateLimitGuard } from '@nestjs-redisx/rate-limit';
@Resolver()
@UseGuards(RateLimitGuard)
export class UserResolver {
@Query(() => [User])
@RateLimit({ points: 100, duration: 60 })
async users() {
return this.userService.findAll();
}
}WebSocket Support
WARNING
The built-in RateLimitGuard uses context.switchToHttp() for both key extraction and setting response headers. For WebSocket contexts, you must:
- Provide a custom
keyfunction that handles the WebSocket execution context - Set
includeHeaders: falseinRateLimitPluginoptions (since WebSocket has no HTTP response headers)
// Plugin config — disable HTTP headers for WebSocket
new RateLimitPlugin({
includeHeaders: false, // Required for non-HTTP contexts
})import { WebSocketGateway, SubscribeMessage } from '@nestjs/websockets';
import { UseGuards } from '@nestjs/common';
import { RateLimitGuard, RateLimit } from '@nestjs-redisx/rate-limit';
@WebSocketGateway()
@UseGuards(RateLimitGuard)
export class ChatGateway {
@SubscribeMessage('message')
@RateLimit({
points: 10,
duration: 60,
key: (ctx) => {
const client = ctx.switchToWs().getClient();
return `ws:${client.id}`;
},
})
handleMessage(client: any, payload: any) {
return { event: 'message', data: payload };
}
}Microservices Support
WARNING
For microservice contexts, you must:
- Provide a custom
keyfunction — the built-in key extractors (ip,user,apiKey) rely on HTTP request objects - Set
includeHeaders: falseinRateLimitPluginoptions (since RPC contexts have no HTTP response headers)
// Plugin config — disable HTTP headers for microservice contexts
new RateLimitPlugin({
includeHeaders: false, // Required for non-HTTP contexts
})import { Controller, UseGuards } from '@nestjs/common';
import { MessagePattern, Ctx, RmqContext } from '@nestjs/microservices';
import { RateLimitGuard, RateLimit } from '@nestjs-redisx/rate-limit';
@Controller()
@UseGuards(RateLimitGuard)
export class UserController {
@MessagePattern({ cmd: 'get_user' })
@RateLimit({
points: 1000,
duration: 60,
key: (ctx) => {
const data = ctx.switchToRpc().getData();
return `rpc:${data.id || 'unknown'}`;
},
})
async getUser(data: { id: string }) {
return this.userService.findOne(data.id);
}
}Next Steps
- Service API — Programmatic API
- Key Extraction — Custom keys