Rate Limiting
Protect your endpoints against abuse with configurable rate limiting. Bunstone supports request limiting at multiple levels, with in-memory or Redis-backed storage.
Overview
Bunstone's rate limiting system provides:
- Multiple configuration levels: Global, Controller, or Endpoint
- Flexible storage: Memory (default) or Redis (production)
- Smart identification: IP + Method + Endpoint
- Automatic headers: Limit information on every response
- Customizable messages: Customize the 429 error message
Basic Usage
Per Endpoint with @RateLimit()
Use the @RateLimit() decorator to apply specific limits to individual endpoints:
typescript
import { Controller, Get, Post, RateLimit } from "@grupodiariodaregiao/bunstone";
@Controller("api")
export class ApiController {
@Get("public")
@RateLimit({ max: 100, windowMs: 60000 }) // 100 requests/minute
getPublic() {
return { data: [] };
}
@Post("sensitive")
@RateLimit({ max: 5, windowMs: 60000 }) // 5 requests/minute (more restrictive)
createSensitive() {
return { success: true };
}
}Global Configuration
Apply rate limiting across the entire application via AppStartup.create():
typescript
const app = await AppStartup.create(AppModule, {
rateLimit: {
enabled: true,
max: 1000,
windowMs: 60000, // 1000 requests/minute for all endpoints
},
});Configuration Options
@RateLimit() Decorator
typescript
@RateLimit({
max: 100, // Maximum requests within the window
windowMs: 60000, // Time window in milliseconds (1 minute)
message?: string, // Custom message when exceeded (optional)
storage?: Storage, // Custom storage (optional)
keyGenerator?: fn, // Function to generate the identification key (optional)
skipHeader?: string, // Header that allows bypass (optional)
skip?: fn // Function to skip rate limiting (optional)
})Global Configuration
typescript
{
rateLimit: {
enabled?: boolean, // Enable/disable global rate limiting
max?: number, // Maximum requests (default: 100)
windowMs?: number, // Window in ms (default: 60000)
storage?: Storage, // Custom storage
keyGenerator?: fn, // Custom key generator
skipHeader?: string, // Bypass header
skip?: fn, // Bypass function
message?: string // Error message
}
}Storage
MemoryStorage (Default)
Ideal for development and single-instance applications:
typescript
// No configuration required - this is the default
@RateLimit({ max: 100, windowMs: 60000 })RedisStorage
For production applications with multiple instances:
typescript
import { RedisStorage } from "@grupodiariodaregiao/bunstone";
import Redis from "ioredis"; // or "redis"
const redisClient = new Redis({
host: "localhost",
port: 6379,
});
const app = await AppStartup.create(AppModule, {
rateLimit: {
enabled: true,
max: 1000,
windowMs: 60000,
storage: new RedisStorage(redisClient, "ratelimit:"), // optional prefix
},
});Response Headers
All responses include informative headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1706640000When the limit is exceeded (HTTP 429):
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706640000
Retry-After: 45
{ "status": 429, "message": "Too many requests, please try again later." }Advanced Use Cases
Custom Identification Key
By default, the key is IP:Method:Path. You can customize it:
typescript
@RateLimit({
max: 100,
windowMs: 60000,
keyGenerator: (req) => {
// Rate limit by authenticated user instead of IP
return req.headers["x-user-id"] || req.ip;
},
})Bypass via Header
Allow bypass in internal environments:
typescript
@RateLimit({
max: 100,
windowMs: 60000,
skipHeader: "x-internal-request", // Requests with this header ignore the limit
})Conditional Bypass
Custom logic to skip rate limiting:
typescript
@RateLimit({
max: 100,
windowMs: 60000,
skip: (req) => {
// Skip for internal IPs
return req.ip?.startsWith("10.0.0.");
},
})Custom Messages
typescript
@RateLimit({
max: 5,
windowMs: 60000,
message: "You have reached the attempt limit. Please wait 1 minute.",
})Configuration Hierarchy
Settings are applied in the following precedence order:
@RateLimit()decorator (highest precedence)- Controller configuration (if implemented)
- Global configuration in
AppStartup.create() - No rate limit (default if no configuration is provided)
Merge example:
typescript
// Global configuration: 1000 req/min
const app = await AppStartup.create(AppModule, {
rateLimit: { enabled: true, max: 1000, windowMs: 60000 },
});
@Controller("api")
class ApiController {
@Get("strict")
@RateLimit({ max: 10 }) // Uses 10 req/min (overrides global)
strictEndpoint() {}
@Get("default")
defaultEndpoint() {} // Uses 1000 req/min (inherits global)
}Complete Example
ts
import {
Module,
Controller,
Get,
Post,
AppStartup,
RateLimit,
MemoryStorage,
} from "../../index";
/**
* Example demonstrating Rate Limiting features
*
* Features:
* - Endpoint-level rate limiting with @RateLimit()
* - Global rate limiting configuration
* - Custom rate limit messages
* - Rate limit headers in responses
*/
@Controller("api")
class ApiController {
/**
* Public endpoint with strict rate limit (5 requests per minute)
* Returns rate limit headers:
* - X-RateLimit-Limit: 5
* - X-RateLimit-Remaining: 4 (decreases with each request)
* - X-RateLimit-Reset: timestamp
*/
@Get("public")
@RateLimit({ max: 5, windowMs: 60000, message: "Too many requests. Please slow down." })
getPublic() {
return { message: "This endpoint is rate limited to 5 requests per minute" };
}
/**
* Premium endpoint with higher rate limit (100 requests per minute)
*/
@Get("premium")
@RateLimit({ max: 100, windowMs: 60000 })
getPremium() {
return { message: "Premium users get 100 requests per minute" };
}
/**
* Write operation with very strict limit (3 requests per minute)
*/
@Post("create")
@RateLimit({ max: 3, windowMs: 60000 })
createResource() {
return { message: "Resource created", id: "123" };
}
/**
* Unprotected endpoint - no rate limit applied
*/
@Get("unlimited")
getUnlimited() {
return { message: "This endpoint has no rate limiting" };
}
}
@Module({
controllers: [ApiController],
})
class AppModule {}
// Example 1: No global rate limit (only decorator-based limits)
const app1 = await AppStartup.create(AppModule);
console.log("Example 1: Decorator-only rate limits");
// Example 2: With global rate limit (applies to ALL endpoints)
const app2 = await AppStartup.create(AppModule, {
rateLimit: {
enabled: true,
max: 1000, // 1000 requests per window
windowMs: 60000, // per minute
message: "Global rate limit exceeded",
},
});
console.log("Example 2: Global rate limit (1000 req/min for all endpoints)");
// Example 3: Custom storage (Redis example - requires Redis connection)
// const redisClient = new Redis(); // from 'ioredis' or 'redis'
// const app3 = await AppStartup.create(AppModule, {
// rateLimit: {
// enabled: true,
// max: 100,
// windowMs: 60000,
// storage: new RedisStorage(redisClient), // For multi-instance deployments
// },
// });
// Start the server
const app = await AppStartup.create(AppModule);
app.listen(3000);
console.log("Rate limiting example running on http://localhost:3000");
console.log("");
console.log("Endpoints:");
console.log(" GET /api/public - 5 req/min (decorator limit)");
console.log(" GET /api/premium - 100 req/min (decorator limit)");
console.log(" POST /api/create - 3 req/min (decorator limit)");
console.log(" GET /api/unlimited - No rate limit");
console.log("");
console.log("Response headers include:");
console.log(" X-RateLimit-Limit - Maximum requests allowed");
console.log(" X-RateLimit-Remaining - Remaining requests in window");
console.log(" X-RateLimit-Reset - Unix timestamp when window resets");
console.log(" Retry-After - Seconds to wait (only when 429)");Production Tips
- Use RedisStorage for multi-instance applications
- Configure skipHeader for health checks and internal monitoring
- Adjust windowMs according to the usage pattern (REST APIs generally use 1 minute)
- Monitor the headers to understand usage patterns
- Informative messages help users understand the limits
API Reference
Classes
RateLimitService- Main rate limiting serviceMemoryStorage- In-memory implementationRedisStorage- Redis implementation
Interfaces
RateLimitStorage- Interface for custom implementationsRateLimitConfig- Rate limit configurationRateLimitInfo- Usage informationRateLimitHeaders- Response headers
Decorators
@RateLimit(options)- Applies rate limiting to an endpoint
Exceptions
RateLimitExceededException- Thrown when the limit is exceeded