We use tracking cookies to understand how you use the product and help us improve it. For more information on how we store cookies, read our  privacy policy.

Add Rate Limiting

Learn how to add rate limiting to protect your API routes

Learn how to use Plainform's built-in rate limiting to protect your API routes from abuse.

Goal

By the end of this recipe, you'll have rate limiting configured on your API routes.

Prerequisites

  • A working Plainform installation

Plainform includes a custom rate limiting implementation in lib/rateLimit.ts. No external dependencies required.

Steps

Choose Rate Limiter

Plainform provides pre-configured rate limiters:

  • rateLimiters.strict - 5 requests per 10 seconds (sensitive operations)
  • rateLimiters.standard - 10 requests per 10 seconds (general API routes)
  • rateLimiters.lenient - 20 requests per 10 seconds (read-only operations)
  • rateLimiters.email - 3 requests per 60 seconds (email sending)

Apply to API Route

Add rate limiting to your API route:

app/api/your-route/route.ts
import { NextRequest, NextResponse } from 'next/server';
import {
  rateLimiters,
  getClientIdentifier,
  createRateLimitResponse,
} from '@/lib/rateLimit';

export async function POST(req: NextRequest) {
  // Get client identifier (IP address)
  const identifier = getClientIdentifier(req);
  
  // Check rate limit
  const rateLimitResult = rateLimiters.standard(identifier);

  if (!rateLimitResult.success) {
    return createRateLimitResponse(rateLimitResult);
  }

  // Your API logic here
  return NextResponse.json({ success: true });
}

Test Rate Limiting

Test by making multiple requests quickly:

# Make 15 requests (should fail after 10 with standard limiter)
for i in {1..15}; do curl http://localhost:3000/api/your-route -X POST; done

The response includes rate limit headers:

  • X-RateLimit-Limit - Maximum requests allowed
  • X-RateLimit-Remaining - Requests remaining
  • X-RateLimit-Reset - Timestamp when limit resets
  • Retry-After - Seconds to wait before retrying

Rate Limiter Examples

Strict (Payment Operations)

app/api/stripe/checkout/route.ts
import { rateLimiters, getClientIdentifier, createRateLimitResponse } from '@/lib/rateLimit';

export async function POST(req: NextRequest) {
  const identifier = getClientIdentifier(req);
  const rateLimitResult = rateLimiters.strict(identifier);

  if (!rateLimitResult.success) {
    return createRateLimitResponse(rateLimitResult);
  }

  // Process payment
}

Email (Newsletter Signup)

app/api/resend/newsletter/route.ts
import { rateLimiters, getClientIdentifier, createRateLimitResponse } from '@/lib/rateLimit';

export async function POST(req: NextRequest) {
  const identifier = getClientIdentifier(req);
  const rateLimitResult = rateLimiters.email(identifier);

  if (!rateLimitResult.success) {
    return createRateLimitResponse(rateLimitResult);
  }

  // Send email
}

Custom Rate Limiter

Create a custom rate limiter for specific needs:

lib/customRateLimit.ts
import { createRateLimit } from '@/lib/rateLimit';

// 100 requests per hour
export const hourlyLimit = createRateLimit(100, 60 * 60 * 1000);

// 1000 requests per day
export const dailyLimit = createRateLimit(1000, 24 * 60 * 60 * 1000);

Then use it in your API route:

import { hourlyLimit } from '@/lib/customRateLimit';
import { getClientIdentifier, createRateLimitResponse } from '@/lib/rateLimit';

export async function GET(req: NextRequest) {
  const identifier = getClientIdentifier(req);
  const rateLimitResult = hourlyLimit(identifier);

  if (!rateLimitResult.success) {
    return createRateLimitResponse(rateLimitResult);
  }

  // Your logic
}

Per-User Rate Limiting

Rate limit by user ID instead of IP:

app/api/protected/route.ts
import { auth } from '@clerk/nextjs/server';
import { rateLimiters, createRateLimitResponse } from '@/lib/rateLimit';

export async function POST(req: NextRequest) {
  const { userId } = await auth();
  
  if (!userId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Use userId as identifier
  const rateLimitResult = rateLimiters.standard(userId);

  if (!rateLimitResult.success) {
    return createRateLimitResponse(rateLimitResult);
  }

  // Your API logic
}

Production Considerations

The built-in rate limiter uses in-memory storage. For production with multiple servers, consider using Redis with Upstash or similar.

Upgrade to Redis

For production deployments with multiple servers:

  1. Install Upstash packages:
npm install @upstash/ratelimit @upstash/redis
  1. Replace the in-memory implementation in lib/rateLimit.ts with Redis-backed storage

  2. See Upstash Rate Limiting docs for implementation details

Common Issues

Rate Limit Not Working

  • Verify the rate limiter is called before your API logic
  • Check that getClientIdentifier() returns a valid identifier
  • Test with multiple requests to trigger the limit

All Requests Blocked

  • Check rate limit configuration is not too strict
  • Verify IP address extraction is working correctly
  • Clear the in-memory store by restarting the dev server

Next Steps

How is this guide ?

Last updated on