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,
})

Rate Limit Configuration

Rate limits are centralized in a configuration file for easy customization:

config/rate-limit.ts
export const rateLimitConfig = {
  actions: {
    // 3 submissions per 24 hours
    submission: { points: 3, duration: 24 * 60 * 60 },
    // 3 newsletter signups per 24 hours
    newsletter: { points: 3, duration: 24 * 60 * 60 },
    // 3 reports per hour
    report: { points: 3, duration: 60 * 60 },
    // 5 claim attempts per hour
    claim: { points: 5, duration: 60 * 60 },
    // 20 media uploads per hour
    media: { points: 20, duration: 60 * 60 },
  },
}

Rate Limiter

The rate limiters are created in lib/rate-limiter.ts based on the config above. Each action gets a RateLimiterRedis (or RateLimiterMemory fallback) instance.

Usage

Use the isRateLimited helper in your server actions:

server/actions/submit.ts
import { isRateLimited } from "~/lib/rate-limiter"

export async function submitAction() {
  if (await isRateLimited("submission")) {
    throw new Error("Too many submissions. Please try again later.")
  }

  // Your submission logic here
}

The rate limiter automatically:

  • Identifies users by their IP address
  • Returns true if the user has exceeded the limit
  • Fails open (returns false) if there's an error, to avoid blocking legitimate users

Custom Identifiers

You can optionally provide a custom key prefix or identifier:

// Use a custom key prefix
await isRateLimited("submission", "premium-submit")

// Use a custom identifier (e.g., user ID instead of IP)
await isRateLimited("submission", undefined, userId)

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