Service API
Use RateLimitService for programmatic control.
Service Injection
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 ApiService {
constructor(
@Inject(RATE_LIMIT_SERVICE)
private readonly rateLimitService: IRateLimitService,
private readonly userService: UserService,
) {}
async checkRateLimit(userId: string): Promise<boolean> {
const result = await this.rateLimitService.check(`user:${userId}`, {
points: 100,
duration: 60,
algorithm: 'sliding-window',
});
return result.allowed;
}
async getRateLimitStatus(userId: string) {
const result = await this.rateLimitService.peek(`user:${userId}`, {
points: 100,
duration: 60,
});
return {
remaining: result.remaining,
limit: result.limit,
resetAt: new Date(result.reset * 1000),
};
}
async resetUserLimit(userId: string): Promise<void> {
await this.rateLimitService.reset(`user:${userId}`);
}
async apiCall(userId: string, isPremium: boolean) {
const config = isPremium
? { points: 1000, duration: 60 }
: { points: 100, duration: 60 };
const result = await this.rateLimitService.check(`user:${userId}`, config);
if (!result.allowed) {
throw new TooManyRequestsException();
}
return { success: true };
}
}check() Method
Check if request is allowed:
typescript
async checkRateLimit(userId: string): Promise<boolean> {
const result = await this.rateLimitService.check(`user:${userId}`, {
points: 100,
duration: 60,
algorithm: 'sliding-window',
});
return result.allowed;
}RateLimitResult
typescript
interface IRateLimitResult {
allowed: boolean;
limit: number;
remaining: number;
reset: number;
retryAfter?: number;
current: number;
}getState() Method
Get human-readable state for monitoring:
typescript
async getStatus(userId: string): Promise<{ current: number; limit: number; resetAt: Date }> {
const state = await this.rateLimitService.getState(`user:${userId}`, {
points: 100,
duration: 60,
});
return {
current: state.current,
limit: state.limit,
resetAt: state.resetAt,
};
}reset() Method
Reset rate limit counter:
typescript
async resetUserLimit(userId: string): Promise<void> {
await this.rateLimitService.reset(`user:${userId}`);
}
// Use case: Premium user upgrade
async upgradeToPremium(userId: string): Promise<void> {
await this.userService.upgrade(userId);
await this.rateLimitService.reset(`user:${userId}`);
}peek() Method
Check status without consuming:
typescript
async getRateLimitStatus(userId: string): Promise<RateLimitStatus> {
const result = await this.rateLimitService.peek(`user:${userId}`, {
points: 100,
duration: 60,
});
return {
remaining: result.remaining,
limit: result.limit,
resetAt: new Date(result.reset * 1000),
};
}Conditional Rate Limiting
typescript
async apiCall(userId: string, isPremium: boolean): Promise<Response> {
const config = isPremium
? { points: 1000, duration: 60 }
: { points: 100, duration: 60 };
const result = await this.rateLimitService.check(`user:${userId}`, config);
if (!result.allowed) {
throw new TooManyRequestsException();
}
return this.makeApiCall();
}Multi-Resource Locking
Check multiple limits:
typescript
async complexOperation(userId: string, orgId: string): Promise<void> {
// Check user limit
const userLimit = await this.rateLimitService.check(`user:${userId}`, {
points: 10,
duration: 60,
});
if (!userLimit.allowed) {
throw new TooManyRequestsException('User limit exceeded');
}
// Check org limit
const orgLimit = await this.rateLimitService.check(`org:${orgId}`, {
points: 100,
duration: 60,
});
if (!orgLimit.allowed) {
throw new TooManyRequestsException('Organization limit exceeded');
}
// Both limits OK, proceed
await this.doComplexOperation();
}Graceful Degradation
typescript
async getData(userId: string): Promise<Response> {
const result = await this.rateLimitService.check(`cache:${userId}`, {
points: 10,
duration: 60,
});
if (result.allowed) {
// Serve from cache (fast)
return this.getCachedData(userId);
} else {
// Fallback to database (slow but available)
return this.getDataFromDB(userId);
}
}Dynamic Limits
Adjust limits based on runtime conditions:
typescript
async dynamicRateLimit(userId: string): Promise<void> {
const user = await this.userService.findOne(userId);
const limits = {
free: { points: 10, duration: 60 },
pro: { points: 100, duration: 60 },
enterprise: { points: 1000, duration: 60 },
};
const config = limits[user.tier] || limits.free;
const result = await this.rateLimitService.check(`user:${userId}`, config);
if (!result.allowed) {
throw new TooManyRequestsException();
}
}Background Jobs
Rate limit background jobs:
typescript
import { Injectable, Inject } from '@nestjs/common';
import { RATE_LIMIT_SERVICE, IRateLimitService } from '@nestjs-redisx/rate-limit';
import { Mailer, EmailQueue } from './types';
@Injectable()
export class EmailService {
constructor(
@Inject(RATE_LIMIT_SERVICE)
private readonly rateLimitService: IRateLimitService,
private readonly mailer: Mailer,
private readonly emailQueue: EmailQueue,
) {}
async sendEmail(to: string, subject: string): Promise<void> {
// Limit email sending to prevent spam
const result = await this.rateLimitService.check('email:send', {
points: 100,
duration: 3600, // 100 emails per hour
});
if (!result.allowed) {
// Queue for later
await this.emailQueue.add(
{ to, subject },
{ delay: (result.retryAfter ?? 60) * 1000 },
);
return;
}
// Send immediately
await this.mailer.send({ to, subject });
}
}Webhook Rate Limiting
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
}
}Testing Support
Mock service for unit tests:
typescript
import { Test } from '@nestjs/testing';
import { describe, it, expect, beforeEach, vi, type MockedObject } from 'vitest';
import { RATE_LIMIT_SERVICE, type IRateLimitService } from '@nestjs-redisx/rate-limit';
describe('ApiService', () => {
let service: ApiService;
let rateLimitService: MockedObject<IRateLimitService>;
beforeEach(async () => {
const mockRateLimitService = {
check: vi.fn(),
peek: vi.fn(),
reset: vi.fn(),
getState: vi.fn(),
};
const module = await Test.createTestingModule({
providers: [
ApiService,
{
provide: RATE_LIMIT_SERVICE,
useValue: mockRateLimitService,
},
],
}).compile();
service = module.get(ApiService);
rateLimitService = module.get(RATE_LIMIT_SERVICE);
});
it('should check rate limit', async () => {
rateLimitService.check.mockResolvedValue({
allowed: true,
remaining: 99,
limit: 100,
reset: Date.now() / 1000 + 60,
current: 1,
});
const result = await service.checkRateLimit('user123');
expect(result).toBe(true);
expect(rateLimitService.check).toHaveBeenCalledWith('user:user123', {
points: 100,
duration: 60,
algorithm: 'sliding-window',
});
});
});Next Steps
- Algorithms — Deep dive into algorithms
- Testing — Test rate-limited code