Skip to content

Rate Limiting (primitive)

ratelimit is a sliding-window rate limiter. Each check(id) is a single atomic Lua round trip that drops expired entries, counts the window, and admits the request if it is under the limit.

schema.ts
import { ratelimit } from "benni/schema";
export const apiLimit = ratelimit("api", { limit: 10, windowMs: 60_000 });
app.ts
const { success, remaining, resetMs } = await redis.query.apiLimit.check(userId);
if (!success) {
throw new Response("Too Many Requests", {
status: 429,
headers: { "Retry-After": String(Math.ceil((resetMs - Date.now()) / 1000)) }
});
}

Declared as a schema value it lands in redis.query and needs no client of its own. Where you hold a client but no handle, such as inside a middleware factory, benni/primitives exports the same limiter in its client-taking form, over the same keys:

import { ratelimit } from "benni/primitives";
const limiter = ratelimit({ client, limit: 10, windowMs: 60_000 });
const { success } = await limiter.check(userId);

client accepts a RedisClient, a promise of one, a factory, or a Benni handle, so it runs over every adapter, including benni/upstash on the edge, which is where rate limiting is most often needed.

type RatelimitResult = {
success: boolean; // is this request allowed?
limit: number; // the configured limit
remaining: number; // requests left in the window (0 when denied)
resetMs: number; // epoch-ms when the window next frees a slot
};

The window is a log of request timestamps in one sorted set (a single key, so it is Redis Cluster safe). That makes the limit exact (no fixed-window boundary bursts) at the cost of storing up to limit entries per key. For typical API limits (tens to hundreds per window) that is ideal; for very high per-key rates, prefer a counter-based limiter.

OptionDefaultMeaning
limit-Maximum requests allowed within the window.
windowMs-Window length in milliseconds.
prefix"ratelimit"Key namespace; keys are <prefix>:<id>.

Use a stable id per subject: a user id, API key, or IP. Each id is limited independently.

See Rate Limiting patterns for the underlying Redis approach if you want to roll your own.