oss-ratelimit
v2.6.0
Published
Flexible rate limiting library with Redis for TypeScript applications
Maintainers
Readme
Okay, let's revamp the README.md based on the current library features and best practices, incorporating the strengths of the old README while updating it significantly.
OSS-Ratelimit 🚀
A robust, production-ready, open-source rate limiting library for Node.js & Next.js, built with TypeScript. Inspired by Upstash Ratelimit but with enhanced features, flexibility, and a powerful registry system.
Features ✨
- Multiple Algorithms: Fixed Window, Sliding Window, and Token Bucket strategies out-of-the-box.
- Efficient Redis Backend: Leverages Redis and Lua scripts for high-performance, distributed rate limiting.
- Client & Registry Management: Easily manage multiple limiter configurations and efficiently reuse Redis connections.
- Ephemeral Cache: Optional in-memory fallback during Redis outages (currently for Sliding Window) for improved resilience.
- Fail Open/Closed Strategy: Configurable behavior during Redis connection issues.
- Analytics: Optional tracking of request counts and throughput per identifier.
- Blocking Support: Includes a
.block()method to wait until a request is permitted. - TypeScript First: Strongly typed for a great developer experience.
- Next.js Ready: Designed for easy integration with Next.js (Pages Router, App Router, Middleware) including IP detection utilities.
- Customizable: Flexible configuration options for fine-tuning behavior.
Installation 📦
Install the library and its peer dependency redis:
npm install oss-ratelimit redis
# or
yarn add oss-ratelimit redis
# or
pnpm add oss-ratelimit redisYou might also need types for Redis:
npm install -D @types/redis
# or
yarn add -D @types/redis
# or
pnpm add -D @types/redisQuick Start
While you can create single instances, we strongly recommend using the Registry system (see below) for managing configurations and Redis clients efficiently in most applications.
Here's a very basic example using a single instance:
import { createClient } from 'redis';
import { Ratelimit, slidingWindow, getRedisClient } from 'oss-ratelimit';
async function run() {
// 1. Get a Redis client (using the helper is convenient)
const redis = await getRedisClient({
url: process.env.RATELIMIT_REDIS_URL || 'redis://localhost:6379'
});
// Ensure you handle connection errors from getRedisClient if needed
// 2. Configure and create the limiter instance
const limiter = new Ratelimit({
redis, // The connected client
limiter: slidingWindow(10, '10 s'), // Allow 10 requests per 10 seconds
prefix: 'quickstart_app', // Optional prefix for keys
});
// 3. Apply the limit
const identifier = 'user:123';
const result = await limiter.limit(identifier);
console.log(`Limit result for ${identifier}:`, result);
// Example Output: { success: true, limit: 10, remaining: 9, reset: 1678886410000 }
if (!result.success) {
console.warn(`Rate limit exceeded for ${identifier}. Retry after ${result.retryAfter}s.`);
}
// Remember to disconnect the client when done
await redis.quit();
}
run().catch(console.error);Recommended Usage: Registry System 🏗️
For managing multiple rate limits (e.g., different API tiers, login vs general API) and simplifying Redis client management, use the built-in registry.
1. Initialize the Registry (initRateLimit)
Define your limiter configurations in one place.
import {
initRateLimit,
RateLimitBuilder,
RegisterConfigParam,
slidingWindow,
fixedWindow,
} from 'oss-ratelimit';
// Define unique names for your limiters
export type LimiterName = 'apiGeneral' | 'loginAttempts' | 'expensiveOp';
// Create the registry instance (configure default Redis connection)
export const rateLimiterRegistry: RateLimitBuilder<LimiterName> = initRateLimit<LimiterName>({
defaultRedisOptions: {
url: process.env.RATELIMIT_REDIS_URL || 'redis://localhost:6379',
},
});
// Define configurations for each limiter name
export const limiterConfigs: Record<LimiterName, RegisterConfigParam> = {
apiGeneral: {
limiter: slidingWindow(50, '30 s'), // 50 req / 30 sec
prefix: 'rl_api_gen',
analytics: true,
},
loginAttempts: {
limiter: fixedWindow(5, '15 m'), // 5 req / 15 min
prefix: 'rl_login',
failOpen: false, // Be strict on login attempts
},
expensiveOp: {
limiter: fixedWindow(10, '1 h'), // 10 req / hour
prefix: 'rl_expensive',
}
};2. Initialize Limiters Eagerly (Recommended)
Load configurations and connect to Redis on startup.
import { rateLimiterRegistry, limiterConfigs } from './lib/ratelimit';
import { initializeLimiters } from 'oss-ratelimit';
async function startServer() {
try {
console.log("Initializing rate limiters...");
// Initialize all limiters defined in the configs
await initializeLimiters({
registry: rateLimiterRegistry,
configs: limiterConfigs,
throwOnError: true, // Exit if any limiter fails to initialize
});
console.log("✅ Rate limiters initialized successfully.");
// ... start your Express/Next.js/other server ...
} catch (error) {
console.error("💥 Failed to initialize rate limiters:", error);
process.exit(1);
}
}
startServer();3. Use the Limiter
Access the specific limiter instance via the registry.
import { rateLimiterRegistry } from '@/lib/ratelimit'; // Adjust path
import { getIpFromRequest } from '@/utils/getIpFromRequest'; // See Docs for this util
// Example in an Express-like handler
async function handleRequest(req: Request, res: Response) {
const ip = getIpFromRequest(req); // Implement IP detection
if (!ip) return res.status(400).send('Cannot determine IP');
try {
// Get the specific limiter instance by name
const limiter = rateLimiterRegistry.get('apiGeneral'); // Throws if not initialized
const result = await limiter.limit(ip);
// Add rate limit headers to response (recommended)
res.setHeader('X-RateLimit-Limit', result.limit);
res.setHeader('X-RateLimit-Remaining', result.remaining);
// ... other headers
if (!result.success) {
res.setHeader('Retry-After', result.retryAfter ?? 1);
return res.status(429).send('Too Many Requests');
}
// Proceed with protected logic
res.send('Success!');
} catch (error) {
console.error("Rate limit check failed:", error);
res.status(500).send('Internal Server Error');
}
}➡️ Learn more about the Registry System in the Docs
Available Limiters
Quick examples of configuring different algorithms:
Fixed Window
import { fixedWindow } from 'oss-ratelimit';
// 100 requests per minute
const limiterConfig = fixedWindow(100, '1 m');Sliding Window
import { slidingWindow } from 'oss-ratelimit';
// 50 requests per 30 seconds (rolling window)
const limiterConfig = slidingWindow(50, '30 s');Token Bucket
import { tokenBucket } from 'oss-ratelimit';
// Refill 5 tokens every 10 seconds, bucket capacity of 20
const limiterConfig = tokenBucket(5, '10 s', 20);➡️ Read Algorithm Details in the Docs
API Highlights
const limiter = rateLimiterRegistry.get('apiGeneral');
const identifier = 'user:xyz';
// Check limit (consumes 1 request if successful)
const { success, limit, remaining, reset, retryAfter, pending, throughput } = await limiter.limit(identifier);
// Block until request is allowed (or timeout)
try {
await limiter.block(identifier, { maxWaitMs: 3000 }); // Wait up to 3s
// Proceed...
} catch (e) { /* Handle RateLimitExceededError */ }
// Reset the limit for an identifier
await limiter.reset(identifier);
// Check status without consuming request
const stats = await limiter.getStats(identifier); // { used, remaining, limit, reset }
const isAllowed = await limiter.check(identifier); // booleanConfiguration
Key options when creating a Ratelimit instance or registering with the registry:
redis: ConnectedRedisClientTypeinstance orRedisOptions.limiter: Algorithm configuration (fixedWindow(...), etc.).prefix: String prefix for Redis keys (default:open-ratelimit).analytics:boolean(default:false) - Enable extra metrics.timeout:number(default:1000) - Redis command timeout (ms).ephemeralCache:boolean(default:true) - Enable in-memory fallback (Sliding Window only).ephemeralCacheTTL:number(default:60000) - TTL for cache entries (ms).failOpen:boolean(default:false) - Allow requests if Redis fails?silent:boolean(default:false) - Suppress warnings (e.g., cache usage).
➡️ See Full Configuration Options in the Docs
Error Handling ⚠️
The library uses specific error types:
RatelimitError: Base error class.RedisConnectionError: Issues connecting/communicating with Redis (whenfailOpen: false).RateLimitExceededError: Thrown by.block()on timeout.
Handle errors gracefully using try...catch:
try {
await limiter.limit("user-123");
} catch (error) {
if (error instanceof RedisConnectionError) {
console.error("Redis down!", error);
// Return 503 or handle based on failOpen config
} else if (error instanceof RatelimitError) {
console.error("Rate limit configuration or operational error:", error);
// Return 500
} else {
console.error("Unexpected error:", error);
// Return 500
}
}➡️ Learn more about Error Handling & Events in the Docs
Next.js Integration
Easily integrate with Next.js API Routes, Middleware, or App Router Handlers. A utility function for reliable IP detection is recommended.
➡️ See Next.js Integration Guide in the Docs
Contributing 🤝
Contributions are welcome! Please read the Contributing Guidelines before submitting a PR.
- Fork the repository.
- Create your feature branch (
git checkout -b feature/your-feature). - Commit your changes (
git commit -m 'feat: Add some feature'). - Push to the branch (
git push origin feature/your-feature). - Open a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
