Documentation

Rate Limiting

Learn how to set up Redis for efficient rate limiting with automatic fallback

Dirstarter uses rate-limiter-flexible with Redis for efficient rate limiting. This provides a flexible solution for protecting your APIs and routes from abuse, with automatic in-memory fallback when Redis is unavailable.

This integration is optional. If Redis is not configured, rate limiting will automatically fall back to in-memory storage.

Redis Setup

Redis is used for distributed rate limiting across multiple server instances. You can use any standard Redis provider:

Configuration

Add the following environment variable to connect to your Redis instance:

.env
# Format: redis://[username:password@]host:port
REDIS_URL="redis://localhost:6379"

If REDIS_URL is not set, rate limiting will automatically use in-memory storage. This works fine for single-server deployments but won't share state across multiple instances.

Implementation

Redis Service

The Redis client is configured in services/redis.ts as a singleton (similar to the Prisma client pattern). Key configuration:

services/redis.ts
const client = new Redis(env.REDIS_URL, {
  maxRetriesPerRequest: null,
  lazyConnect: true,
  retryStrategy: () => null,
})

Per-Procedure Limits

Rate limits are declared per procedure via meta.rateLimit on the oRPC builder — there is no central config file. Pass { points, duration } (duration in seconds), or "none" to disable limiting for that procedure:

server/tools/router.ts
import { authedProcedure } from "~/server/orpc/procedure"

// 3 submissions per 24 hours
const submit = authedProcedure
  .meta({ permission: "tools.submit", rateLimit: { points: 3, duration: 24 * 60 * 60 } })
  .input(toolSubmitSchema)
  .handler(/* ... */)

Every procedure inherits a default bucket of { points: 600, duration: 3600 } (600 requests per hour) from the base builder; override it per procedure as shown above.

How It Works

A single gate (consumeRateLimit in server/orpc/rate-limit.ts) runs before each handler:

  • Identity — the caller's user id when signed in, otherwise their client IP
  • Scope — buckets are namespaced by the procedure path, so two procedures that share the same { points, duration } numbers keep independent quotas
  • Bypass — admins and in-process React Server Component calls (source: "rsc") skip limiting
  • Backing storeRateLimiterRedis when REDIS_URL is set, with an in-memory fallback
  • On exhaustion it throws a TOO_MANY_REQUESTS error carrying retryAfterSeconds

The public REST API (/api/v1) adds a separate global envelope of 600 requests/hour per API key on top of these per-procedure limits.

Last updated on

On this page

Join hundreds of directory builders

Build your directory, launch, earn

Don't waste time on Stripe subscriptions or designing a pricing section. Get started today with our battle-tested stack and built-in monetization features.

Get Lifetime Access