Skip to main content

RateLimiter middlewares

Initial configuration​

To begin using the rate-limiter middlewares, you'll need to create and configure a RateLimiterFactoryResolver instance:

./samples/rate-limiter.ts
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​

./samples/with-rate-limiter.ts
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.

info

Here is a complete list of settings for the withRateLimiter function.

Settings​

OptionTypeDescription
keyInvocable<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
limitnumberMaximum number of invocations allowed within the configured window
onlyErrorbooleanWhen true, only failed (errored) invocations count toward the rate limit. Defaults to false
errorPolicyErrorPolicyDetermines 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​

./samples/with-http-rate-limiter-error.ts
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​

ErrorsStatus
BlockedRateLimiterError429
UnableToGetStateRateLimiterError, UnableToUpdateStateRateLimiterError, UnableToResetRateLimiterError503

A 429 response means the rate limiter is blocking requests until the quota resets. The response carries the standard rate-limit headers:

HeaderMeaning
RateLimitThe request quota for the current policy
Retry-AfterHow 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.