RateLimiter middlewares
Initial configuration
To begin using the rate-limiter middlewares, you'll need to create and configure a RateLimiterFactoryResolver instance:
import { RateLimiterFactoryResolver } from "eridu-tech/rate-limiter";
import { DatabaseRateLimiterAdapter } from "eridu-tech/rate-limiter/database-rate-limiter-adapter";
import { MemoryRateLimiterStorageAdapter } from "eridu-tech/rate-limiter/memory-rate-limiter-storage-adapter";
import { RedisRateLimiterAdapter } from "eridu-tech/rate-limiter/redis-rate-limiter-adapter";
import { SuperJsonSerde } from "eridu-tech/serde/super-json-serde";
import { Redis } from "ioredis";
const serde = new SuperJsonSerde();
export const rateLimiterFactoryResolver = new RateLimiterFactoryResolver({
serde,
adapters: {
memory: new DatabaseRateLimiterAdapter({
adapter: new MemoryRateLimiterStorageAdapter(),
}),
redis: new RedisRateLimiterAdapter({
database: new Redis("YOUR_REDIS_CONNECTION_STRING"),
}),
},
defaultAdapter: "memory",
});
await rateLimiterFactoryResolver.init();
withRateLimiterFactory middleware
The RateLimiter middleware wraps function calls with a rate limiter, controlling how many times a function can be invoked within a configured policy window. Each unique key (derived from the function's arguments) gets its own rate limit counter. Once the limit is reached, further invocations are blocked until the policy permits attempts again.
Usage
import { withRateLimiterFactory } from "eridu-tech/rate-limiter/middlewares";
import { use } from "eridu-tech/middleware";
import { rateLimiterFactoryResolver } from "./rate-limiter.js";
const withRateLimiter = withRateLimiterFactory(rateLimiterFactoryResolver);
const fetchHandler = async (request: Request): Promise<Response> => {
// ... handle the request
return new Response("OK");
};
// Wrap with rate limiter using the default adapter (`memory`) — max 10 calls per window
const rateLimitedCall = use(
fetchHandler,
withRateLimiter({
key: ([req]) => `api:${String(req.headers.get("x-ip"))}`,
limit: 10,
}),
);
// Wrap with rate limiter using a specific adapter (`redis`)
const rateLimitedCallOnRedis = use(
fetchHandler,
withRateLimiter.use("redis")({
key: ([req]) => `api:${String(req.headers.get("x-ip"))}`,
limit: 10,
}),
);
await rateLimitedCall(
new Request("/url", {
method: "POST",
}),
);
await rateLimitedCallOnRedis(
new Request("/url", {
method: "POST",
}),
);
Calling withRateLimiter() directly uses the resolver's default adapter (memory). Use withRateLimiter.use("redis") to run the wrapped function through a specific registered adapter.
Here is a complete list of settings for the withRateLimiter function.
Settings
| Option | Type | Description |
|---|---|---|
key | Invocable<TParameters, string> | A function that produces the rate-limiter key from the wrapped function's arguments. Each unique key gets its own rate-limit counter |
limit | number | Maximum number of invocations allowed within the configured window |
onlyError | boolean | When true, only failed (errored) invocations count toward the rate limit. Defaults to false |
errorPolicy | ErrorPolicy | Determines which errors count toward the rate limit. Defaults to treating all errors as failures |
withHttpRateLimiterError middleware
The withHttpRateLimiterError middleware converts every rate-limiter error thrown by the wrapped endpoint or downstream middleware into an HttpError, so the request fails with a meaningful status code instead of a generic 500. Errors that are not rate-limiter errors are rethrown untouched, and the original error is preserved as the cause.
Usage
import { HttpRouter, defaultHttpRouterAdapter } from "eridu-tech/http-router";
import { BlockedRateLimiterError } from "eridu-tech/rate-limiter/contracts";
import { withHttpRateLimiterError } from "eridu-tech/rate-limiter/middlewares";
import { TimeSpan } from "eridu-tech/time-span";
const router = new HttpRouter({
router: defaultHttpRouterAdapter(),
});
router.use(withHttpRateLimiterError());
router.endpoint({
url: "/reports/1",
method: ["POST"],
handler: async () => {
// A 429 response with the `RateLimit` and `Retry-After` headers
throw BlockedRateLimiterError.create(
{
limit: 10,
totalAttempts: 12,
exceedAttempts: 2,
retryAfter: TimeSpan.fromSeconds(30),
},
"tenant:1",
);
},
});
Status codes
| Errors | Status |
|---|---|
BlockedRateLimiterError | 429 |
UnableToGetStateRateLimiterError, UnableToUpdateStateRateLimiterError, UnableToResetRateLimiterError | 503 |
A 429 response means the rate limiter is blocking requests until the quota resets. The response carries the standard rate-limit headers:
| Header | Meaning |
|---|---|
RateLimit | The request quota for the current policy |
Retry-After | How many seconds to wait before retrying |
A 503 response means the rate-limiter adapter failed, so the same request can be retried later.
Further information
For further information refer to eridu-tech/rate-limiter API docs.