Skip to Content
LimiterSelf-Hosting

Self-Hosting on Cloudflare Workers

Follow these steps to run Limiter on Cloudflare Workers.
While we work on the documentation, you can use this example as the basis for running on other serverless platforms such as AWS Lambda, Vercel, or Supabase.

Prerequisites

1. Install Dependencies

npm install @borrowdev/node @upstash/redis

2. Configure wrangler.jsonc

{ "$schema": "node_modules/wrangler/config-schema.json", "name": "borrow-limiter", "compatibility_date": "2025-04-25", "main": "src/index.ts", "vars": { // Use this if you're using Upstash Redis. "UPSTASH_REDIS_REST_URL": "https://your-upstash-redis-url", } }

3. Configure your secrets

For local development, you should store your secrets in a .dev.vars file in the root of your project.
Do not commit this file to your version control system.
For more information, check out the official secrets documentation.

BORROW_LIMITER_INVOKE_SECRET="random-secure-string" UPSTASH_REDIS_REST_TOKEN="your-upstash-redis-token" UPSTASH_REDIS_REST_URL="https://your-upstash-redis-url"

4. Create the src/index.ts file

import { limiter, UpstashRedisAdapter } from "@borrowdev/node/limiter/host"; import { Redis } from "@upstash/redis/cloudflare"; export default { async fetch(request, env, ctx) { const redis = Redis.fromEnv(env); const adapters = { storage: new UpstashRedisAdapter(redis) }; const req = await request.json(); // Execute the limiter function, passing: // - invokeSecret from env for authentication // - the Redis-backed storage adapter // - ctx.waitUntil for background updates (or false if you want to execute synchronously) const response = await limiter({ env, req: { ...req || {}, invokeSecret: request.headers.get("X-Borrow-Api-Key") || "", } as any, adapters, backgroundExecute: ctx.waitUntil.bind(ctx), hooks: { beforeResponse: async (r) => console.log("Limiter result: ", r), }, }); if (response.error) { console.error("Limiter error: ", response.message); } // Return JSON result return new Response( JSON.stringify({ result: response.result, timeLeft: response.timeLeft, tokensLeft: response.tokensLeft, }), { status: response.status, headers: { "Content-Type": "application/json" }, }, ); }, } satisfies ExportedHandler<any>;

5. Call the Limiter API from your application server

You can now call the Limiter API by using our client library or by making an HTTP request.

import { borrow } from "@borrowdev/node"; const { success, timeLeft } = await borrow.limiter("my-limiter-id", "current-user-id", { limiters: [{ maxTokens: 20, tokensCost: 5, tokensPerReplenish: 10, interval: "minute", type: "token", }], options: { // Use for testing or if you want to fail closed. failBehavior: "fail", apiKey: "random-secure-string", endpoint: { baseUrl: "http://localhost:8787", } } }); if (!success) { return { message: "Rate limit exceeded." + (timeLeft !== null ? ` You can try again in ${timeLeft} seconds.` : "") }; } // ... Your expensive business logic

6. Use another database (optional)

If you want to use a different database than Upstash Redis, you can write your own adapter.
You just need to extend the StorageAdapter class and implement the required methods.

import { StorageAdapter } from "@borrowdev/node/limiter/host"; class MyCustomAdapter extends StorageAdapter { private db: DbType; constructor(db: DbType) { super(); this.db = db; } override get = (key: string) => { // Implement your logic to get a value using the given input. Must not throw. } override set = async (key: string, value: any) => { // Implement your logic to set a value using the given inputs. May throw. } override relative = async (key: string, field: string, amount: number) => { // Implement your logic to increment/decrement an integer using the given inputs. May throw. } // (optional) override getStorageKey = ( params: { limiterType: LimiterType; userId: string | null; key: string | null; } ) => { // Implement your logic to generate a storage key using the given parameters. May throw. } }