Protokit

Rate Limiting

Optional API rate limiting with tier-based limits and graceful degradation

Rate Limiting

Protect your API from abuse with tier-based rate limiting. This feature is completely optional and works without any configuration.

You don't need this for development or early-stage projects. Skip this page and come back when you have users and need protection.


How It Works

With Rate LimitingWithout (Default)
Limits requests per user tierAll requests allowed
Protects against API abuseNo protection
Returns 429 when exceededNever returns 429
Requires Upstash RedisNo external service needed

Graceful Degradation

Without Redis configured, the system automatically allows all requests. No errors, no warnings, just works.

// This always succeeds when Redis is not configured
const result = await checkRateLimit("user-123", 100, "60 s");
// result.success = true (always)

When to Enable

SituationRecommendation
DevelopmentSkip - Focus on building features
MVP / BetaSkip - Get users first
< 100 usersSkip - Not necessary yet
Production with trafficConsider - Good protection
Experiencing abuseEnable - You need this

Quick Setup

1. Create Upstash Account

  1. Go to console.upstash.com
  2. Create a free Redis database (10,000 requests/day free)
  3. Copy the REST URL and Token

2. Add Environment Variables

UPSTASH_REDIS_REST_URL="https://your-db.upstash.io"
UPSTASH_REDIS_REST_TOKEN="your-token"

3. Done

Rate limiting is now active. No code changes needed.


Rate Limit Tiers

Limits are based on subscription tier:

TierRequestsWindowUse Case
Free101 minuteBasic usage
Pro1001 minuteRegular users
Enterprise10001 minuteHigh-volume
Anonymous201 minuteIP-based

Authentication Endpoints

Stricter limits to prevent brute-force attacks:

EndpointRequestsWindow
Login51 minute
Register31 minute
Password Reset31 minute

Response Headers

Every API response includes rate limit information:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 2024-01-15T10:30:00.000Z

When limit is exceeded (429 response):

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
Retry-After: 45

Client-Side Handling

async function fetchAPI(url: string) {
  const response = await fetch(url);
 
  if (response.status === 429) {
    const retryAfter = response.headers.get("Retry-After");
    console.log(`Rate limited. Retry in ${retryAfter}s`);
 
    // Wait and retry
    await new Promise((r) => setTimeout(r, Number(retryAfter) * 1000));
    return fetchAPI(url);
  }
 
  return response.json();
}

Customization

Custom Endpoint Limits

Edit src/lib/rate-limit/config.ts:

export const ENDPOINT_RATE_LIMITS = {
  "/api/auth/callback": { requests: 5, window: "60 s" },
  "/api/heavy-operation": { requests: 2, window: "60 s" },
  // Add your custom endpoints
};

Bypass Routes

Some routes skip rate limiting:

export const RATE_LIMIT_BYPASS_ROUTES = [
  "/api/health", // Health checks
  "/api/stripe/webhook", // Webhooks with signature verification
];

API Reference

Check Rate Limit

import { checkRateLimit } from "@/lib/rate-limit";
 
const result = await checkRateLimit(
  "user:123:action", // Unique identifier
  10, // Max requests
  "60 s" // Time window
);
 
if (!result.success) {
  // Handle rate limit exceeded
}

With Route Handler

import { withRateLimit } from "@/lib/rate-limit/middleware";
 
async function handler(request: NextRequest) {
  return NextResponse.json({ success: true });
}
 
export const GET = withRateLimit(handler, {
  getUserId: async (req) => session?.user?.id,
  getTier: async (req) => "pro",
});

Cost

ProviderFree TierPaid
Upstash10,000 req/day~$0.20/100k req

For most projects, the free tier is sufficient.


Alternatives

If you prefer self-hosting:

  1. Redis + ioredis: Requires code refactoring
  2. In-memory: Only works for single-instance deployments
  3. PostgreSQL: Slower but no external service

The current implementation uses Upstash for simplicity and serverless compatibility.


Next Steps