Beignet API reference
    Preparing search index...

    Module @beignet/provider-rate-limit-upstash

    @beignet/provider-rate-limit-upstash

    Upstash-backed RateLimitPort provider for Beignet applications.

    The provider installs ctx.ports.rateLimit using Upstash Redis and @upstash/ratelimit.

    • Implements the standard RateLimitPort interface.
    • Uses the Upstash Redis REST API, so it is serverless-friendly.
    • Supports dynamic limits per request with a configurable key prefix.
    • Emits devtools events for allowed, blocked, and failed hits.
    bun add @beignet/provider-rate-limit-upstash @upstash/redis @upstash/ratelimit
    

    Set these environment variables:

    Variable Required Description Example
    UPSTASH_REDIS_REST_URL Yes Your Upstash Redis REST URL https://us1-properly-ancient-12345.upstash.io
    UPSTASH_REDIS_REST_TOKEN Yes Your Upstash Redis REST token AXXXeyJpZCI6IjEy...
    UPSTASH_PREFIX No Key prefix for rate limit keys (default: ck:ratelimit) myapp:ratelimit
    1. Sign up at Upstash
    2. Create a new Redis database
    3. Navigate to the database details page
    4. Copy the REST URL and REST token from the "REST API" section
    import { createNextServer } from "@beignet/next";
    import { upstashRateLimitProvider } from "@beignet/provider-rate-limit-upstash";
    import { createRateLimitHooks } from "@beignet/core/server";
    import type { AppContext } from "@/app-context";
    import { appPorts } from "@/infra/app-ports";
    import { routes } from "@/server/routes";

    export const server = await createNextServer({
    ports: appPorts,
    providers: [upstashRateLimitProvider],
    hooks: [createRateLimitHooks<AppContext>()],
    createContext: ({ ports }) => ({ ports }),
    routes,
    });

    Once the provider is registered, you can use the rate limit port in hooks, policies, or use cases:

    // Example app-specific policy that rate limits by IP address
    async function checkIpRateLimit(ctx: AppCtx) {
    const result = await ctx.ports.rateLimit.hit({
    key: `ip:${ctx.ip}`,
    limit: 100,
    windowSec: 60, // 100 requests per 60 seconds
    });

    if (!result.allowed) {
    return {
    status: 429,
    headers: {
    "X-RateLimit-Limit": "100",
    "X-RateLimit-Remaining": String(result.remaining ?? 0),
    "X-RateLimit-Reset": result.resetAt?.toISOString() ?? "",
    "Retry-After": String(result.retryAfterSeconds ?? 0),
    },
    body: {
    code: "TOO_MANY_REQUESTS",
    message: "Rate limit exceeded. Please try again later.",
    },
    };
    }

    // Request is allowed
    return undefined;
    }

    You can apply different rate limits for different operations:

    // Strict rate limit for auth endpoints
    const loginResult = await ctx.ports.rateLimit.hit({
    key: `login:${ctx.ip}`,
    limit: 5,
    windowSec: 300, // 5 attempts per 5 minutes
    });

    // More relaxed rate limit for API endpoints
    const apiResult = await ctx.ports.rateLimit.hit({
    key: `api:user:${userId}`,
    limit: 1000,
    windowSec: 3600, // 1000 requests per hour
    });

    You can define rate limit metadata on your contracts:

    const getTodos = api.get("/todos")
    .meta({
    rateLimit: { max: 60, windowSec: 60, scope: "user" },
    });

    The built-in createRateLimitHooks(...) helper reads this metadata and applies the limit through ctx.ports.rateLimit. If your app needs custom behavior, keep the same metadata shape and call the port directly:

    type RateLimitMetadata = {
    rateLimit?: {
    max: number;
    windowSec: number;
    scope?: "global" | "ip" | "user";
    };
    };

    async function rateLimitFromMeta(ctx: AppCtx, meta?: RateLimitMetadata) {
    if (!meta?.rateLimit) return;

    const { max, windowSec, scope = "global" } = meta.rateLimit;
    const actorId =
    ctx.actor?.type === "user" && ctx.actor.id ? ctx.actor.id : undefined;
    const result = await ctx.ports.rateLimit.hit({
    key:
    scope === "user"
    ? `user:${actorId ?? "anonymous"}`
    : `${scope}:${ctx.ip ?? "global"}`,
    limit: max,
    windowSec,
    });

    if (!result.allowed) {
    return {
    status: 429,
    body: {
    code: "TOO_MANY_REQUESTS",
    message: "Too many requests",
    },
    };
    }
    }

    The hit method returns a RateLimitResult with:

    interface RateLimitResult {
    allowed: boolean; // true if the hit is within the limit
    remaining: number | null; // requests remaining in the window
    resetAt: Date | null; // when the window resets
    retryAfterSeconds: number | null; // retry delay when the hit is rejected
    }
    • Algorithm: Uses fixed window rate limiting via Ratelimit.fixedWindow()
    • Backend: Upstash Redis REST API (serverless-compatible)
    • Per-request configuration: Creates a new Ratelimit instance for each hit() call to support dynamic limits
    • Key prefix: Configurable prefix to avoid key collisions

    When @beignet/devtools is installed before this provider, rate limit checks appear under the dashboard's Rate limits watcher.

    The provider records rateLimit.hit events with the key, limit, window, configured prefix, allowed/blocked result, remaining count, reset time, retry-after value, and duration. Provider failures are recorded as rateLimit.hit.failed.

    The provider extends the standard RateLimitPort with access to the underlying Upstash Redis client:

    import type { UpstashRateLimitPort } from "@beignet/provider-rate-limit-upstash";

    const rateLimit = ctx.ports.rateLimit as UpstashRateLimitPort;

    // Access the Redis client for advanced operations
    await rateLimit.client.get("some:key");
    await rateLimit.client.set("some:key", "value");

    The provider includes comprehensive tests. Run them with:

    bun test
    

    MIT

    Interfaces

    CreateUpstashRateLimitPortOptions
    UpstashRateLimitPort

    Type Aliases

    UpstashRateLimitConfig

    Variables

    upstashRateLimitProvider