javascript 45 lines · 8 steps

Sliding-window rate limiting in a Next.js route

A Next.js API route caps requests per IP using a Redis sorted set that tracks each hit's timestamp.

Explained by highlit
1import { NextResponse } from 'next/server';
2import { Redis } from '@upstash/redis';
3 
4const redis = Redis.fromEnv();
5 
6const WINDOW_MS = 60_000;
7const MAX_REQUESTS = 30;
8 
9async function checkRateLimit(ip) {
10 const now = Date.now();
11 const windowStart = now - WINDOW_MS;
12 const key = `ratelimit:${ip}`;
13 
14 const pipeline = redis.multi();
15 pipeline.zremrangebyscore(key, 0, windowStart);
16 pipeline.zadd(key, { score: now, member: `${now}-${Math.random()}` });
17 pipeline.zcard(key);
18 pipeline.pexpire(key, WINDOW_MS);
19 
20 const [, , count] = await pipeline.exec();
21 
22 return {
23 allowed: count <= MAX_REQUESTS,
24 remaining: Math.max(0, MAX_REQUESTS - count),
25 };
26}
27 
28export async function GET(request) {
29 const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? '127.0.0.1';
30 const { allowed, remaining } = await checkRateLimit(ip);
31 
32 const headers = {
33 'X-RateLimit-Limit': String(MAX_REQUESTS),
34 'X-RateLimit-Remaining': String(remaining),
35 };
36 
37 if (!allowed) {
38 return NextResponse.json(
39 { error: 'Too many requests' },
40 { status: 429, headers: { ...headers, 'Retry-After': String(WINDOW_MS / 1000) } },
41 );
42 }
43 
44 return NextResponse.json({ data: 'ok' }, { headers });
45}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A sorted set scored by timestamp turns rate limiting into a running count of hits inside a moving time window.
  2. 2Batching Redis commands in a pipeline makes the read-modify-count sequence a single atomic round trip.
  3. 3Returning limit and remaining headers lets clients self-throttle before they ever hit a 429.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Sliding-window rate limiting in a Next.js route — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code