A flexible and powerful rate limiting solution for NestJS applications with Redis support
📚 Full Documentation | API Reference | Examples
- ✅ NestJS Integration - Seamless integration with NestJS ecosystem via decorators, guards, and interceptors
- ✅ Redis Support - Distributed rate limiting using Redis with automatic fallback to memory-based limiting
- ✅ TypeScript - Full type safety and IntelliSense support
- ✅ Flexible Configuration - Per-route rate limits with customizable time windows
- ✅ Multiple Patterns - Use decorators, guards, or interceptors based on your needs
- ✅ Tested - Comprehensive test coverage with 33 passing tests
pnpm add @shinijs/rate-limitThis package requires the following peer dependencies to be installed in your project:
| Package | Version | Required |
|---|---|---|
@nestjs/common |
^11.0.0 |
Yes |
@nestjs/config |
^4.0.0 |
Yes |
reflect-metadata |
^0.2.0 |
Yes |
rxjs |
^7.8.0 |
Yes |
ioredis |
^5.0.0 |
No (Optional) |
Install all required peer dependencies:
pnpm add @nestjs/common@^11.0.0 @nestjs/config@^4.0.0 reflect-metadata@^0.2.0 rxjs@^7.8.0For Redis support (recommended for production):
pnpm add ioredis@^5.0.0import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RateLimitModule } from '@shinijs/rate-limit';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
RateLimitModule.forRoot(), // Use forRoot() for configuration options
],
})
export class AppModule {}Create a .env file:
REDIS_URL=redis://localhost:6379If Redis is not configured, the library will automatically fall back to memory-based rate limiting that properly tracks hits in memory.
To use @shinijs/logger with rate limiting for consistent logging:
import { Module, Global } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { LoggerModule, LoggerFactory } from '@shinijs/logger';
import { RateLimitModule } from '@shinijs/rate-limit';
export const RATE_LIMIT_LOGGER_TOKEN = Symbol('RATE_LIMIT_LOGGER');
@Global()
@Module({
providers: [
{
provide: RATE_LIMIT_LOGGER_TOKEN,
useFactory: (loggerFactory: LoggerFactory) => {
return loggerFactory.createLogger('RateLimit');
},
inject: [LoggerFactory],
},
],
exports: [RATE_LIMIT_LOGGER_TOKEN],
})
class RateLimitLoggerModule {}
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
LoggerModule,
RateLimitLoggerModule,
RateLimitModule.forRoot({
loggerToken: RATE_LIMIT_LOGGER_TOKEN, // Inject custom logger
}),
],
})
export class AppModule {}import { Controller, Get } from '@nestjs/common';
import { RateLimit } from '@shinijs/rate-limit';
@Controller('api')
export class ApiController {
@Get('endpoint')
@RateLimit({ requests: 10, window: '1m' }) // 10 requests per minute
async getData() {
return { message: 'Success' };
}
}import { Controller, Get, UseGuards } from '@nestjs/common';
import { RateLimit, RateLimitGuard } from '@shinijs/rate-limit';
@Controller('api')
@UseGuards(RateLimitGuard)
export class ApiController {
@Get('endpoint')
@RateLimit({ requests: 100, window: '1h' }) // 100 requests per hour
async getData() {
return { message: 'Success' };
}
}import { Injectable } from '@nestjs/common';
import { RateLimitService } from '@shinijs/rate-limit';
@Injectable()
export class YourService {
constructor(private readonly rateLimitService: RateLimitService) {}
async processRequest(userId: string) {
const result = await this.rateLimitService.checkRateLimit(
`user:${userId}`,
{ requests: 50, window: '5m' }
);
if (!result.allowed) {
throw new Error('Rate limit exceeded');
}
// Process the request
return { remaining: result.remaining };
}
}The window parameter accepts the following formats:
s- seconds (e.g.,30s= 30 seconds)m- minutes (e.g.,5m= 5 minutes)h- hours (e.g.,1h= 1 hour)d- days (e.g.,1d= 1 day)
| Variable | Description | Default |
|---|---|---|
REDIS_URL |
Redis connection URL | undefined (falls back to memory-based limiting) |
Example:
REDIS_URL=redis://localhost:6379
# Or with authentication
REDIS_URL=redis://:password@localhost:6379
# Or for Redis cluster
REDIS_URL=redis://localhost:6379/0Configure the rate limit module with optional logger injection.
Options:
interface RateLimitModuleOptions {
loggerToken?: string | symbol; // Token to inject logger from DI container
logger?: LoggerService; // Logger instance to use directly
}Example:
RateLimitModule.forRoot({
loggerToken: YOUR_LOGGER_TOKEN, // Inject custom logger via DI token
})Checks if a request is allowed based on the rate limit configuration.
Parameters:
key- Unique identifier for the rate limit (e.g., user ID, IP address)options- Rate limit configurationrequests- Maximum number of requests allowedwindow- Time window (e.g., '1m', '1h')
Returns:
{
allowed: boolean; // Whether the request is allowed
remaining: number; // Remaining requests in current window
resetTime: number; // Timestamp when the limit resets
totalHits: number; // Total requests in current window
}Decrements the rate limit counter (useful for rolling back failed requests).
Checks if Redis connection is healthy. Returns false if Redis is not configured or unhealthy.
Applies rate limiting to a route handler.
@RateLimit({ requests: 10, window: '1m' })A NestJS guard that enforces rate limits based on the @RateLimit decorator.
A NestJS interceptor that enforces rate limits and adds rate limit headers to responses.
- ✅ Distributed rate limiting across multiple instances
- ✅ Persistent rate limit data
- ✅ Accurate counting
- ✅ Suitable for horizontal scaling
⚠️ Only works for single-instance applications⚠️ Rate limits reset on application restart⚠️ Not suitable for distributed systems- ✅ Properly tracks hits in memory (fixed in latest version)
- ℹ️ Automatically used when Redis is not available
# Unit tests
pnpm test
# Watch mode
pnpm test:watch
# Coverage
pnpm test:cov📚 Full Documentation is available with:
- Complete API reference
- Configuration guide
- Usage examples
- Best practices
Contributions are welcome! Please read our Contributing Guide and Code of Conduct before submitting pull requests.
MIT © Shironex