LogoBoring Template

Rate Limiting

Implement rate limiting using Upstash Redis to prevent abuse and ensure fair usage of resources.

This template uses Upstash Redis for rate limiting. For more information, visit the Upstash Redis documentation.

Setup

Prerequisites

Upstash Redis

🔄 Active Upstash Redis account

Connection Details

🔌 Redis REST URL and token

Environment Setup

⚙️ Configured .env.local file

Environment Variables

Add the following variables to your .env.local file:

.env.local
# Upstash Redis Configuration
UPSTASH_REDIS_REST_URL=your_redis_url
UPSTASH_REDIS_REST_TOKEN=your_redis_token

Ensure your Upstash Redis credentials are kept secure and never committed to version control.

Implementation

Redis Client Setup

Configure the Upstash Redis client in lib/upstash.ts:

lib/upstash.ts
import { Redis } from "@upstash/redis";
 
export const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

Rate Limiter Configuration

Set up rate limiting with sliding window algorithm:

lib/rate-limit.ts
import { Ratelimit } from "@upstash/ratelimit";
import { redis } from "@/lib/upstash";
 
const ratelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(
    RATE_LIMIT_5, // 5 requests
    "1 m" // per 1 minute
  ),
});

The sliding window algorithm provides a balance between fixed window and rolling window rate limiting approaches, offering better protection against traffic spikes.

Usage Examples

Server Action Rate Limiting

Implementing rate limiting in server actions:

server/actions/users.ts
export const createUserAction = async () => {
  try {
    const { user } = await getCurrentUser();
    if (!user) {
      throw new AuthenticationError();
    }
 
    // Create unique identifier for this action
    const identifier = `ratelimit:create-user:${user.id}`;
 
    // Check rate limit
    const { success } = await ratelimit.limit(identifier);
    if (!success) {
      throw new RateLimitError();
    }
 
    // Continue with action...
  } catch (error) {
    // Handle errors...
  }
};

API Route Rate Limiting

Protecting API routes with rate limiting:

app/api/example/route.ts
export async function POST(req: NextRequest) {
  try {
    const { user } = await getCurrentUser();
    if (!user) {
      return responses.notAuthenticatedResponse();
    }
 
    const identifier = `ratelimit:api-route:${user.id}`;
    const { success } = await ratelimit.limit(identifier);
 
    if (!success) {
      return responses.tooManyRequestsResponse();
    }
 
    // Continue with API logic...
  } catch (error) {
    return responses.internalServerErrorResponse();
  }
}

Common Use Cases

Image Uploads

🖼️ Limit the number of image uploads per user per minute

Authentication

🔐 Prevent brute force attacks on authentication endpoints

API Endpoints

🔌 Control access rates to public and private API endpoints

Form Submissions

📝 Prevent spam by limiting form submission frequency

Advanced Configuration

Different Rate Limits

You can create multiple rate limiters with different configurations:

lib/rate-limit.ts
// Strict rate limiter for sensitive operations
export const strictRatelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(3, "5 m"), // 3 requests per 5 minutes
});
 
// Standard rate limiter for regular operations
export const standardRatelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(10, "1 m"), // 10 requests per minute
});
 
// Relaxed rate limiter for less sensitive operations
export const relaxedRatelimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(30, "1 m"), // 30 requests per minute
});

IP-Based Rate Limiting

For public endpoints, you can implement IP-based rate limiting:

app/api/public/route.ts
export async function GET(req: NextRequest) {
  try {
    // Get IP address from request
    const ip = req.headers.get("x-forwarded-for") || req.ip || "anonymous";
 
    // Create identifier based on IP
    const identifier = `ratelimit:public-api:${ip}`;
 
    // Check rate limit
    const { success } = await ratelimit.limit(identifier);
 
    if (!success) {
      return responses.tooManyRequestsResponse();
    }
 
    // Continue with API logic...
  } catch (error) {
    return responses.internalServerErrorResponse();
  }
}

When using IP-based rate limiting, be aware of potential issues with shared IPs (e.g., users behind NAT) and implement appropriate limits.

Best Practices

Implementation

  • Use unique identifiers per action
  • Include user ID in rate limit keys
  • Set appropriate time windows
  • Handle rate limit errors gracefully

Security

  • Implement rate limiting early in request pipeline
  • Use different limits for different actions
  • Monitor rate limit usage
  • Provide clear feedback to users

Error Handling

Create custom error types and response handlers for rate limiting:

lib/errors.ts
export class RateLimitError extends ApiError {
  constructor(message = "Too many requests. Please try again later.") {
    super(429, message);
  }
}

Provide user-friendly responses when rate limits are exceeded:

lib/responses.ts
export const responses = {
  // ... other responses
 
  tooManyRequestsResponse: (
    message = "Too many requests. Please try again later."
  ) => {
    return NextResponse.json({ success: false, message }, { status: 429 });
  },
};

Monitoring

Monitor rate limit usage to identify potential abuse patterns:

// Log rate limit attempts
const { success, limit, reset, remaining } = await ratelimit.limit(identifier);
 
if (!success) {
  console.log(
    `Rate limit exceeded for ${identifier}. Limit: ${limit}, Reset: ${reset}`
  );
}
 
// Track near-limits for potential abuse
if (remaining < limit * 0.2) {
  console.log(
    `Rate limit nearly exceeded for ${identifier}. Remaining: ${remaining}/${limit}`
  );
}

Additional Resources