Recipes
Common use cases and production patterns.
1. Login Protection
Prevent brute force attacks:
typescript
@Injectable()
export class AuthController {
@Post('login')
@RateLimit({
key: (ctx) => {
const req = ctx.switchToHttp().getRequest();
return `login:${req.body.email}`;
},
points: 5,
duration: 900, // 5 attempts per 15 minutes
message: 'Too many login attempts. Try again in 15 minutes.',
})
async login(@Body() dto: LoginDto) {
const user = await this.authService.validateUser(dto.email, dto.password);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
return this.authService.login(user);
}
@Post('reset-password')
@RateLimit({
key: 'global:reset-password',
points: 10,
duration: 3600, // 10 resets per hour globally
})
async resetPassword(@Body() dto: ResetPasswordDto) {
return this.authService.sendResetEmail(dto.email);
}
}2. API Tiered Limits
Different limits for different subscription tiers using the Service API:
typescript
import { Injectable, Inject } from '@nestjs/common';
import { RATE_LIMIT_SERVICE, IRateLimitService } from '@nestjs-redisx/rate-limit';
import { TooManyRequestsException } from '../types';
@Injectable()
export class TieredRateLimitService {
private readonly limits: Record<string, { points: number; duration: number }> = {
free: { points: 100, duration: 3600 }, // 100/hour
pro: { points: 1000, duration: 3600 }, // 1K/hour
enterprise: { points: 10000, duration: 3600 }, // 10K/hour
};
constructor(
@Inject(RATE_LIMIT_SERVICE)
private readonly rateLimitService: IRateLimitService,
) {}
async checkTieredLimit(userId: string, tier: string): Promise<void> {
const config = this.limits[tier] || this.limits.free;
const result = await this.rateLimitService.check(
`${tier}:${userId}`,
config,
);
if (!result.allowed) {
throw new TooManyRequestsException(
`Rate limit exceeded. Retry in ${result.retryAfter}s`,
);
}
}
}3. Webhook Rate Limiting
Protect webhook endpoints:
typescript
import { Injectable, Inject } from '@nestjs/common';
import { RATE_LIMIT_SERVICE, IRateLimitService } from '@nestjs-redisx/rate-limit';
import { TooManyRequestsException } from '../types';
@Injectable()
export class WebhookService {
constructor(
@Inject(RATE_LIMIT_SERVICE)
private rateLimitService: IRateLimitService,
) {}
async processWebhook(sourceUrl: string, payload: any): Promise<void> {
// Rate limit by source URL
const result = await this.rateLimitService.check(
`webhook:${sourceUrl}`,
{
algorithm: 'token-bucket',
points: 100, // Bucket capacity
refillRate: 10, // 10 per second sustained
},
);
if (!result.allowed) {
throw new TooManyRequestsException(
`Webhook from ${sourceUrl} exceeded rate limit. Retry in ${result.retryAfter}s`,
);
}
await this.processPayload(payload);
}
private async processPayload(payload: any): Promise<void> {
// Process the webhook payload
}
}4. File Upload Limits
Control upload frequency:
typescript
@Controller('files')
export class FileController {
@Post('upload')
@RateLimit({
key: 'user',
algorithm: 'token-bucket',
points: 10, // 10 concurrent uploads
refillRate: 0.5, // 1 upload every 2 seconds
})
@UseInterceptors(FileInterceptor('file'))
async uploadFile(
@UploadedFile() file: Express.Multer.File,
@Req() req: any,
) {
return this.fileService.save(file, req.user.id);
}
@Post('bulk-upload')
@RateLimit({
key: 'user',
points: 3,
duration: 3600, // 3 bulk uploads per hour
message: 'Bulk upload limited to 3 per hour',
})
async bulkUpload(
@UploadedFiles() files: Express.Multer.File[],
@Req() req: any,
) {
return this.fileService.bulkSave(files, req.user.id);
}
}5. GraphQL Rate Limiting
Rate limit GraphQL queries:
typescript
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { UseGuards } from '@nestjs/common';
import { RateLimitGuard } from '@nestjs-redisx/rate-limit';
@Resolver(() => User)
@UseGuards(RateLimitGuard)
export class UserResolver {
// Read operations: generous limit
@Query(() => [User])
@RateLimit({ key: 'user', points: 1000, duration: 60 })
async users() {
return this.userService.findAll();
}
// Expensive operations: strict limit
@Query(() => UserAnalytics)
@RateLimit({ key: 'user', points: 10, duration: 3600 })
async userAnalytics(@Args('userId') userId: string) {
return this.analyticsService.generate(userId);
}
// Mutations: moderate limit
@Mutation(() => User)
@RateLimit({ key: 'user', points: 100, duration: 60 })
async updateUser(@Args('input') input: UpdateUserInput) {
return this.userService.update(input);
}
}Query Complexity Based Limiting
typescript
@Injectable()
export class ComplexityBasedRateLimiter {
async checkComplexity(query: string, userId: string): Promise<boolean> {
const complexity = this.calculateComplexity(query);
const result = await this.rateLimitService.check(`user:${userId}`, {
points: Math.ceil(complexity), // Consume points based on complexity
duration: 60,
});
return result.allowed;
}
private calculateComplexity(query: string): number {
// Calculate query complexity based on depth, fields, etc.
return 1;
}
}6. Multi-Layer Protection
Combine global and per-user limits:
typescript
@Controller('api')
@RateLimit({
key: 'global:api',
points: 10000,
duration: 60, // Global cap: 10K requests per minute
})
export class ApiController {
@Get('data')
@RateLimit({ key: 'ip', points: 100, duration: 60 }) // Per-IP: 100/min
@RateLimit({ key: 'user', points: 50, duration: 60 }) // Per-user: 50/min
getData() {
// Must pass all three limits
return { data: 'value' };
}
@Get('expensive')
@RateLimit({ key: 'user', points: 5, duration: 60 })
@RateLimit({ key: 'global:expensive', points: 50, duration: 60 })
expensiveOperation() {
return { result: 'computed' };
}
}7. Burst Protection
Allow initial burst, then throttle:
typescript
@Post('process')
@RateLimit({
algorithm: 'token-bucket',
points: 50, // Allow burst of 50
refillRate: 5, // Then 5 per second sustained
key: 'user',
})
async processData(@Body() data: any) {
return this.processor.process(data);
}8. Time-Based Limits
Different limits for different times using the Service API:
typescript
import { Injectable, Inject } from '@nestjs/common';
import { RATE_LIMIT_SERVICE, IRateLimitService } from '@nestjs-redisx/rate-limit';
import { TooManyRequestsException } from '../types';
@Injectable()
export class TimeBasedRateLimiter {
constructor(
@Inject(RATE_LIMIT_SERVICE)
private readonly rateLimitService: IRateLimitService,
) {}
async checkTimeBasedLimit(userId: string): Promise<void> {
const hour = new Date().getHours();
// More generous limits during off-peak hours
const points = hour >= 22 || hour < 6 ? 1000 : 100;
const result = await this.rateLimitService.check(`user:${userId}`, {
points,
duration: 3600,
});
if (!result.allowed) {
throw new TooManyRequestsException();
}
}
}9. Progressive Rate Limiting
Increase limits as user trust grows using the Service API:
typescript
import { Injectable, Inject } from '@nestjs/common';
import { RATE_LIMIT_SERVICE, IRateLimitService } from '@nestjs-redisx/rate-limit';
import { TooManyRequestsException } from '../types';
@Injectable()
export class ProgressiveRateLimiter {
constructor(
@Inject(RATE_LIMIT_SERVICE)
private readonly rateLimitService: IRateLimitService,
) {}
async checkProgressiveLimit(user: { id: string; createdAt: Date }): Promise<void> {
const accountAge = Date.now() - user.createdAt.getTime();
const daysOld = accountAge / (1000 * 60 * 60 * 24);
let points: number;
if (daysOld < 7) points = 50; // New accounts: 50/hour
else if (daysOld < 30) points = 100; // 1 week+: 100/hour
else if (daysOld < 90) points = 500; // 1 month+: 500/hour
else points = 1000; // 3 months+: 1K/hour
const result = await this.rateLimitService.check(`user:${user.id}`, {
points,
duration: 3600,
});
if (!result.allowed) {
throw new TooManyRequestsException();
}
}
}10. Organization-Wide Limits
Limit entire organizations:
typescript
import { Injectable, Inject } from '@nestjs/common';
import { RATE_LIMIT_SERVICE, IRateLimitService } from '@nestjs-redisx/rate-limit';
import { TooManyRequestsException, UserService } from '../types';
@Injectable()
export class OrganizationRateLimiter {
constructor(
@Inject(RATE_LIMIT_SERVICE)
private rateLimitService: IRateLimitService,
private userService: UserService,
) {}
async checkOrganizationLimit(userId: string): Promise<void> {
const user = await this.userService.findOne(userId);
const orgId = user.organizationId;
// Check org-wide limit
const orgResult = await this.rateLimitService.check(`org:${orgId}`, {
points: 10000,
duration: 3600, // 10K requests per hour for entire org
});
if (!orgResult.allowed) {
throw new TooManyRequestsException(
'Organization rate limit exceeded',
);
}
// Also check per-user limit within org
const userResult = await this.rateLimitService.check(
`org:${orgId}:user:${userId}`,
{
points: 1000,
duration: 3600, // 1K per user
},
);
if (!userResult.allowed) {
throw new TooManyRequestsException('User rate limit exceeded');
}
}
}Next Steps
- Troubleshooting — Debug issues
- Overview — Back to overview