Composable rate limiting for Deno/Oak with pluggable storage backends.
import { createRateLimiter, MemoryStorageAdapter } from "@psyque/rate-limiter";
const storage = new MemoryStorageAdapter();
const limiter = createRateLimiter(storage, {
prefix: "api",
maxAttempts: 100,
windowSeconds: 60,
});
router.get("/api/data", limiter, handler);Zero-dependency, works out of the box. Suitable for testing and single-process deployments.
import { MemoryStorageAdapter } from "@psyque/rate-limiter";
const storage = new MemoryStorageAdapter();For production multi-process / multi-node deployments. Inject any Redis-compatible client.
import { connect } from "@db/redis";
import { RedisStorageAdapter } from "@psyque/rate-limiter";
const redis = await connect({ hostname: "localhost", port: 6379 });
const storage = new RedisStorageAdapter(redis);Implement the StorageAdapter interface for any backend:
import type { StorageAdapter } from "@psyque/rate-limiter";
class MyStorageAdapter implements StorageAdapter {
async get(key: string): Promise<string | null> {/* ... */}
async set(key: string, value: string, ttlSeconds: number): Promise<void> {
/* ... */
}
async increment(key: string): Promise<number> {/* ... */}
async decrement(key: string): Promise<number> {/* ... */}
async delete(key: string): Promise<void> {/* ... */}
async ttl(key: string): Promise<number> {/* ... */}
}Use RateLimiterFactoryImpl for a factory pattern with multiple limiter types:
import {
KeyGenerationStrategy,
MemoryStorageAdapter,
RateLimiterFactoryImpl,
} from "@psyque/rate-limiter";
const storage = new MemoryStorageAdapter();
const factory = new RateLimiterFactoryImpl(storage);
const limiter = factory.createRateLimiter({
serviceName: "my-service",
enabled: true,
addHeaders: true,
keyGenerationStrategy: KeyGenerationStrategy.USER_BASED,
thresholds: {
general: { maxAttempts: 200, windowSeconds: 60 },
write: { maxAttempts: 50, windowSeconds: 60 },
apiKey: { maxAttempts: 500, windowSeconds: 60 },
},
});
// Apply to routes
router.get("/", limiter.getGeneralLimiter(), handler);
router.post("/", limiter.getWriteLimiter(), handler);
router.use(limiter.getSmartLimiter()); // auto-selects by methodFor service-oriented architectures with per-endpoint overrides:
import {
createServiceRateLimiter,
MemoryStorageAdapter,
} from "@psyque/rate-limiter";
const storage = new MemoryStorageAdapter();
const limiters = createServiceRateLimiter(storage, {
serviceName: "comment",
general: { maxAttempts: 300, windowSeconds: 60 },
write: { maxAttempts: 60, windowSeconds: 60 },
apiKey: { maxAttempts: 600, windowSeconds: 60 },
endpoints: {
search: { maxAttempts: 30, windowSeconds: 60 },
},
});
router.get("/", limiters.general, handler);
router.post("/", limiters.write, handler);
router.get("/search", limiters.forEndpoint("search"), handler);| Strategy | Description |
|---|---|
IP_BASED |
Rate limit by client IP address (default) |
USER_BASED |
Rate limit by user ID, fallback to IP |
API_KEY_BASED |
Rate limit by API key → user ID → IP |
COMBINED |
Uses both user ID and IP together |
All middleware automatically sets standard rate-limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests allowed |
X-RateLimit-Remaining |
Requests remaining in window |
X-RateLimit-Reset |
Unix timestamp when window resets |
Retry-After |
Seconds until retry (only on 429) |
MIT