Add Rate Limiting

Learn how to add rate limiting in Plainform 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/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

On this page