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:
- Railway - Simple Redis hosting with generous free tier
- Upstash - Serverless Redis with pay-per-use pricing
- AWS ElastiCache - Managed Redis for AWS infrastructure
- DigitalOcean Redis - Managed Redis clusters
- Self-hosted Redis - Run your own Redis instance
Configuration
Add the following environment variable to connect to your Redis instance:
# 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:
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:
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 store —
RateLimiterRediswhenREDIS_URLis set, with an in-memory fallback - On exhaustion it throws a
TOO_MANY_REQUESTSerror carryingretryAfterSeconds
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