[{"data":1,"prerenderedAt":355},["ShallowReactive",2],{"blog-how-to/implement-rate-limiting":3},{"id":4,"title":5,"body":6,"category":335,"date":336,"dateModified":337,"description":338,"draft":339,"extension":340,"faq":341,"featured":339,"headerVariant":342,"image":341,"keywords":341,"meta":343,"navigation":344,"ogDescription":345,"ogTitle":341,"path":346,"readTime":341,"schemaOrg":347,"schemaType":348,"seo":349,"sitemap":350,"stem":351,"tags":352,"twitterCard":353,"__hash__":354},"blog/blog/how-to/implement-rate-limiting.md","How to Implement Rate Limiting in Your API",{"type":7,"value":8,"toc":307},"minimark",[9,13,18,22,31,36,39,55,59,62,82,95,108,112,115,126,138,142,145,151,155,159,162,166,169,173,176,182,186,190,196,200,206,210,216,225,229,268,272,278,282,288],[10,11],"category-badge",{"category":12},"How-To Guide",[14,15,17],"h1",{"id":16},"how-to-implement-rate-limiting","How to Implement Rate Limiting",[19,20,21],"p",{},"Stop abuse before it stops your server",[23,24,25,28],"tldr",{},[19,26,27],{},"TL;DR",[19,29,30],{},"Use Upstash Rate Limit for serverless apps or express-rate-limit for traditional Node.js. Track requests by IP or user ID. Return 429 status when limits are exceeded. Start with conservative limits (100 requests/minute) and adjust based on real usage.",[32,33,35],"h2",{"id":34},"why-rate-limiting-matters","Why Rate Limiting Matters",[19,37,38],{},"Without rate limiting, a single user or bot can:",[40,41,42,46,49,52],"ul",{},[43,44,45],"li",{},"Overwhelm your server with requests",[43,47,48],{},"Run up your API costs (OpenAI, Stripe, etc.)",[43,50,51],{},"Brute force passwords or API keys",[43,53,54],{},"Scrape your entire database",[32,56,58],{"id":57},"option-1-upstash-rate-limit-serverless","Option 1: Upstash Rate Limit (Serverless)",[19,60,61],{},"Best for Vercel, Netlify, and other serverless platforms.",[63,64,66,71],"step",{"number":65},"1",[67,68,70],"h3",{"id":69},"install-the-package","Install the package",[72,73,78],"pre",{"className":74,"code":76,"language":77},[75],"language-text","npm install @upstash/ratelimit @upstash/redis\n","text",[79,80,76],"code",{"__ignoreMap":81},"",[63,83,85,89],{"number":84},"2",[67,86,88],{"id":87},"create-a-rate-limiter","Create a rate limiter",[72,90,93],{"className":91,"code":92,"language":77},[75],"// lib/ratelimit.ts\nimport { Ratelimit } from '@upstash/ratelimit';\nimport { Redis } from '@upstash/redis';\n\nconst redis = new Redis({\n  url: process.env.UPSTASH_REDIS_REST_URL!,\n  token: process.env.UPSTASH_REDIS_REST_TOKEN!,\n});\n\n// 10 requests per 10 seconds\nexport const ratelimit = new Ratelimit({\n  redis,\n  limiter: Ratelimit.slidingWindow(10, '10 s'),\n  analytics: true,\n});\n",[79,94,92],{"__ignoreMap":81},[63,96,98,102],{"number":97},"3",[67,99,101],{"id":100},"use-in-your-api-route","Use in your API route",[72,103,106],{"className":104,"code":105,"language":77},[75],"// app/api/protected/route.ts\nimport { ratelimit } from '@/lib/ratelimit';\nimport { NextRequest } from 'next/server';\n\nexport async function POST(request: NextRequest) {\n  // Get identifier (IP or user ID)\n  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';\n\n  // Check rate limit\n  const { success, limit, reset, remaining } = await ratelimit.limit(ip);\n\n  if (!success) {\n    return Response.json(\n      { error: 'Too many requests' },\n      {\n        status: 429,\n        headers: {\n          'X-RateLimit-Limit': limit.toString(),\n          'X-RateLimit-Remaining': remaining.toString(),\n          'X-RateLimit-Reset': reset.toString(),\n        }\n      }\n    );\n  }\n\n  // Process the request\n  return Response.json({ message: 'Success' });\n}\n",[79,107,105],{"__ignoreMap":81},[32,109,111],{"id":110},"option-2-express-rate-limit-traditional","Option 2: Express Rate Limit (Traditional)",[19,113,114],{},"Best for Express.js and traditional Node.js servers.",[63,116,117,120],{"number":65},[67,118,70],{"id":119},"install-the-package-1",[72,121,124],{"className":122,"code":123,"language":77},[75],"npm install express-rate-limit\n",[79,125,123],{"__ignoreMap":81},[63,127,128,132],{"number":84},[67,129,131],{"id":130},"create-rate-limit-middleware","Create rate limit middleware",[72,133,136],{"className":134,"code":135,"language":77},[75],"import rateLimit from 'express-rate-limit';\n\n// General API limiter\nconst apiLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 100, // 100 requests per window\n  message: { error: 'Too many requests, try again later' },\n  standardHeaders: true, // Return rate limit info in headers\n  legacyHeaders: false,\n});\n\n// Stricter limiter for auth routes\nconst authLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000, // 1 hour\n  max: 5, // 5 attempts per hour\n  message: { error: 'Too many login attempts' },\n});\n\n// Apply to routes\napp.use('/api/', apiLimiter);\napp.use('/api/auth/', authLimiter);\n",[79,137,135],{"__ignoreMap":81},[32,139,141],{"id":140},"option-3-nextjs-middleware","Option 3: Next.js Middleware",[19,143,144],{},"Apply rate limiting to all routes using middleware.",[72,146,149],{"className":147,"code":148,"language":77},[75],"// middleware.ts\nimport { Ratelimit } from '@upstash/ratelimit';\nimport { Redis } from '@upstash/redis';\nimport { NextRequest, NextResponse } from 'next/server';\n\nconst ratelimit = new Ratelimit({\n  redis: Redis.fromEnv(),\n  limiter: Ratelimit.slidingWindow(20, '10 s'),\n});\n\nexport async function middleware(request: NextRequest) {\n  // Only rate limit API routes\n  if (!request.nextUrl.pathname.startsWith('/api')) {\n    return NextResponse.next();\n  }\n\n  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';\n  const { success } = await ratelimit.limit(ip);\n\n  if (!success) {\n    return NextResponse.json(\n      { error: 'Rate limit exceeded' },\n      { status: 429 }\n    );\n  }\n\n  return NextResponse.next();\n}\n\nexport const config = {\n  matcher: '/api/:path*',\n};\n",[79,150,148],{"__ignoreMap":81},[32,152,154],{"id":153},"rate-limiting-strategies","Rate Limiting Strategies",[67,156,158],{"id":157},"fixed-window","Fixed Window",[19,160,161],{},"Simple but can allow bursts at window boundaries. Example: 100 requests per minute, resets at the start of each minute.",[67,163,165],{"id":164},"sliding-window","Sliding Window",[19,167,168],{},"Smoother distribution, recommended for most use cases. Looks at the last N seconds continuously.",[67,170,172],{"id":171},"token-bucket","Token Bucket",[19,174,175],{},"Allows controlled bursts while maintaining average rate. Good for APIs that need occasional spikes.",[72,177,180],{"className":178,"code":179,"language":77},[75],"// Different strategies with Upstash\nimport { Ratelimit } from '@upstash/ratelimit';\n\n// Fixed window: 10 requests per 10 seconds\nRatelimit.fixedWindow(10, '10 s')\n\n// Sliding window: smoother, recommended\nRatelimit.slidingWindow(10, '10 s')\n\n// Token bucket: allows bursts\nRatelimit.tokenBucket(10, '10 s', 5) // 5 tokens max burst\n",[79,181,179],{"__ignoreMap":81},[32,183,185],{"id":184},"choosing-what-to-limit-by","Choosing What to Limit By",[67,187,189],{"id":188},"ip-address","IP Address",[72,191,194],{"className":192,"code":193,"language":77},[75],"// Most common, but can affect shared IPs\nconst ip = request.headers.get('x-forwarded-for') ??\n           request.headers.get('x-real-ip') ??\n           '127.0.0.1';\nconst identifier = ip.split(',')[0].trim();\n",[79,195,193],{"__ignoreMap":81},[67,197,199],{"id":198},"user-id-authenticated","User ID (Authenticated)",[72,201,204],{"className":202,"code":203,"language":77},[75],"// Better for logged-in users\nconst session = await getSession(request);\nconst identifier = session?.user?.id ?? ip;\n",[79,205,203],{"__ignoreMap":81},[67,207,209],{"id":208},"api-key","API Key",[72,211,214],{"className":212,"code":213,"language":77},[75],"// For public APIs\nconst apiKey = request.headers.get('x-api-key');\nconst identifier = apiKey ?? ip;\n",[79,215,213],{"__ignoreMap":81},[217,218,219,222],"info-box",{},[19,220,221],{},"Tip: Different Limits for Different Routes",[19,223,224],{},"Apply stricter limits to sensitive endpoints like login, password reset, and payment processing. Be more generous with read-only endpoints.",[32,226,228],{"id":227},"recommended-limits","Recommended Limits",[40,230,231,238,244,250,256,262],{},[43,232,233,237],{},[234,235,236],"strong",{},"General API:"," 100 requests/minute",[43,239,240,243],{},[234,241,242],{},"Login attempts:"," 5 per hour per IP",[43,245,246,249],{},[234,247,248],{},"Password reset:"," 3 per hour per email",[43,251,252,255],{},[234,253,254],{},"File uploads:"," 10 per hour",[43,257,258,261],{},[234,259,260],{},"AI/LLM calls:"," 20 per minute (cost protection)",[43,263,264,267],{},[234,265,266],{},"Public read API:"," 1000 per minute",[32,269,271],{"id":270},"handling-rate-limit-errors","Handling Rate Limit Errors",[72,273,276],{"className":274,"code":275,"language":77},[75],"// Client-side handling\nasync function fetchWithRetry(url: string, options?: RequestInit) {\n  const response = await fetch(url, options);\n\n  if (response.status === 429) {\n    const retryAfter = response.headers.get('Retry-After');\n    const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 60000;\n\n    // Show user-friendly message\n    throw new Error(`Rate limited. Please wait ${Math.ceil(waitTime / 1000)} seconds.`);\n  }\n\n  return response;\n}\n",[79,277,275],{"__ignoreMap":81},[32,279,281],{"id":280},"testing-rate-limits","Testing Rate Limits",[72,283,286],{"className":284,"code":285,"language":77},[75],"# Bash: Send many requests quickly\nfor i in {1..20}; do\n  curl -s -o /dev/null -w \"%{http_code}\\n\" http://localhost:3000/api/test\ndone\n\n# You should see 200s then 429s\n",[79,287,285],{"__ignoreMap":81},[289,290,291,297,302],"related-articles",{},[292,293],"related-card",{"description":294,"href":295,"title":296},"Complete guide to configuring environment variables in Vercel. Set up secrets for production, preview, and development e","/blog/how-to/vercel-env-vars","How to Set Up Vercel Environment Variables",[292,298],{"description":299,"href":300,"title":301},"Step-by-step guide to adding security headers on Vercel. Configure via vercel.json, Next.js middleware, and edge functio","/blog/how-to/vercel-headers","How to Configure Security Headers on Vercel",[292,303],{"description":304,"href":305,"title":306},"Step-by-step guide to input validation with Zod. Schema definition, API validation, form validation with React Hook Form","/blog/how-to/zod-validation","How to Validate Input with Zod",{"title":81,"searchDepth":308,"depth":308,"links":309},2,[310,311,317,321,322,327,332,333,334],{"id":34,"depth":308,"text":35},{"id":57,"depth":308,"text":58,"children":312},[313,315,316],{"id":69,"depth":314,"text":70},3,{"id":87,"depth":314,"text":88},{"id":100,"depth":314,"text":101},{"id":110,"depth":308,"text":111,"children":318},[319,320],{"id":119,"depth":314,"text":70},{"id":130,"depth":314,"text":131},{"id":140,"depth":308,"text":141},{"id":153,"depth":308,"text":154,"children":323},[324,325,326],{"id":157,"depth":314,"text":158},{"id":164,"depth":314,"text":165},{"id":171,"depth":314,"text":172},{"id":184,"depth":308,"text":185,"children":328},[329,330,331],{"id":188,"depth":314,"text":189},{"id":198,"depth":314,"text":199},{"id":208,"depth":314,"text":209},{"id":227,"depth":308,"text":228},{"id":270,"depth":308,"text":271},{"id":280,"depth":308,"text":281},"how-to","2026-01-16","2026-02-04","Step-by-step guide to implementing rate limiting. Protect your API from abuse with Upstash, Redis, or in-memory solutions. Includes Next.js and Express examples.",false,"md",null,"yellow",{},true,"Protect your API from abuse with rate limiting. Upstash, Redis, and in-memory solutions.","/blog/how-to/implement-rate-limiting","[object Object]","HowTo",{"title":5,"description":338},{"loc":346},"blog/how-to/implement-rate-limiting",[],"summary_large_image","A2BD3PJFVnYzytRYGZXSrHZZXetcRioQXeHX7fAs5iM",1775843928262]