MyApp

Getting Started

Introduction

Content & Marketing

Add Blog PostAdd Blog AuthorAdd TestimonialsCustomize HeroAdd FAQ Items

Payments

Add Stripe ProductCreate Stripe SubscriptionAdd Stripe CouponCustomize CheckoutTest Payments Locally

Authentication

Customize Sign-InAdd OAuthImplement RolesProtect Routes

Content Management

Add Doc PageCreate Doc SectionCustomize Theme

Customization

Change ColorsAdd FontCustomize EmailsUse PostHog Analytics

Deployment

Deploy VercelDatabase Migrations

Advanced

Server ActionsAdd Rate Limiting
MyApp

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

  • Server Actions - Use server actions for mutations

Related Documentation

  • API Routes - Next.js API routes

How is this guide ?

Last updated on

Server Actions

Learn how to use Next.js Server Actions for mutations

On this page

Goal
Prerequisites
Steps
Choose Rate Limiter
Apply to API Route
Test Rate Limiting
Rate Limiter Examples
Strict (Payment Operations)
Email (Newsletter Signup)
Custom Rate Limiter
Per-User Rate Limiting
Production Considerations
Upgrade to Redis
Common Issues
Rate Limit Not Working
All Requests Blocked
Next Steps
Related Documentation