[{"data":1,"prerenderedAt":414},["ShallowReactive",2],{"blog-best-practices/rate-limiting":3},{"id":4,"title":5,"body":6,"category":390,"date":391,"dateModified":391,"description":392,"draft":393,"extension":394,"faq":395,"featured":393,"headerVariant":399,"image":400,"keywords":400,"meta":401,"navigation":402,"ogDescription":403,"ogTitle":400,"path":404,"readTime":405,"schemaOrg":406,"schemaType":407,"seo":408,"sitemap":409,"stem":410,"tags":411,"twitterCard":412,"__hash__":413},"blog/blog/best-practices/rate-limiting.md","Rate Limiting Best Practices: API Protection and Abuse Prevention",{"type":7,"value":8,"toc":378},"minimark",[9,20,29,34,37,71,75,78,93,97,100,109,113,116,125,129,132,141,145,148,203,207,269,298,320,324,327,347,366],[10,11,12],"tldr",{},[13,14,15,19],"p",{},[16,17,18],"strong",{},"The #1 rate limiting best practice is applying different limits for different endpoints"," - strict limits for auth and expensive operations, more lenient for public reads. Use sliding windows, identify users by authenticated ID (not just IP), return proper 429 responses with Retry-After headers, and use distributed stores like Redis for multi-server deployments.",[21,22,23],"quotable-box",{},[24,25,26],"blockquote",{},[13,27,28],{},"\"Rate limiting is your API's immune system. Without it, a single bad actor can bring down your entire service and bankrupt your cloud budget.\"",[30,31,33],"h2",{"id":32},"why-rate-limiting-matters","Why Rate Limiting Matters",[13,35,36],{},"Rate limiting protects against:",[38,39,40,47,53,59,65],"ul",{},[41,42,43,46],"li",{},[16,44,45],{},"Brute force attacks:"," Login attempts, password resets",[41,48,49,52],{},[16,50,51],{},"DDoS:"," Overwhelming your servers",[41,54,55,58],{},[16,56,57],{},"Scraping:"," Automated data extraction",[41,60,61,64],{},[16,62,63],{},"API abuse:"," Excessive usage beyond plan limits",[41,66,67,70],{},[16,68,69],{},"Cost attacks:"," Running up your cloud bills",[30,72,74],{"id":73},"best-practice-1-different-limits-for-different-endpoints-5-min","Best Practice 1: Different Limits for Different Endpoints 5 min",[13,76,77],{},"Not all endpoints need the same limits:",[79,80,82],"code-block",{"label":81},"Express rate limiting by endpoint",[83,84,89],"pre",{"className":85,"code":87,"language":88},[86],"language-text","import rateLimit from 'express-rate-limit';\n\n// General API: 100 requests per 15 minutes\nconst apiLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 100,\n  standardHeaders: true,\n  legacyHeaders: false,\n});\n\n// Login: 5 attempts per 15 minutes\nconst loginLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: { error: 'Too many login attempts' },\n  skipSuccessfulRequests: true,\n});\n\n// Password reset: 3 per hour\nconst resetLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000,\n  max: 3,\n});\n\n// Expensive operations: 10 per hour\nconst expensiveLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000,\n  max: 10,\n});\n\n// Apply limits\napp.use('/api/', apiLimiter);\napp.post('/api/auth/login', loginLimiter);\napp.post('/api/auth/reset-password', resetLimiter);\napp.post('/api/generate', expensiveLimiter);\n","text",[90,91,87],"code",{"__ignoreMap":92},"",[30,94,96],{"id":95},"best-practice-2-identify-users-correctly-3-min","Best Practice 2: Identify Users Correctly 3 min",[13,98,99],{},"IP-based limiting is not enough for authenticated APIs:",[79,101,103],{"label":102},"User-based rate limiting",[83,104,107],{"className":105,"code":106,"language":88},[86],"const userLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 100,\n  keyGenerator: (req) => {\n    // Use user ID for authenticated requests\n    if (req.user?.id) {\n      return `user:${req.user.id}`;\n    }\n    // Fall back to IP for unauthenticated\n    return `ip:${req.ip}`;\n  },\n  skip: (req) => {\n    // Skip rate limiting for admins\n    return req.user?.role === 'admin';\n  },\n});\n",[90,108,106],{"__ignoreMap":92},[30,110,112],{"id":111},"best-practice-3-use-redis-for-distributed-systems-5-min","Best Practice 3: Use Redis for Distributed Systems 5 min",[13,114,115],{},"Memory-based rate limiting does not work across multiple servers:",[79,117,119],{"label":118},"Redis-based rate limiting",[83,120,123],{"className":121,"code":122,"language":88},[86],"import rateLimit from 'express-rate-limit';\nimport RedisStore from 'rate-limit-redis';\nimport { createClient } from 'redis';\n\nconst redisClient = createClient({\n  url: process.env.REDIS_URL,\n});\n\nawait redisClient.connect();\n\nconst limiter = rateLimit({\n  store: new RedisStore({\n    sendCommand: (...args) => redisClient.sendCommand(args),\n  }),\n  windowMs: 15 * 60 * 1000,\n  max: 100,\n});\n",[90,124,122],{"__ignoreMap":92},[30,126,128],{"id":127},"best-practice-4-return-proper-headers-2-min","Best Practice 4: Return Proper Headers 2 min",[13,130,131],{},"Help clients understand rate limits:",[79,133,135],{"label":134},"Rate limit response headers",[83,136,139],{"className":137,"code":138,"language":88},[86],"// Response headers to include:\nRateLimit-Limit: 100              // Max requests allowed\nRateLimit-Remaining: 42           // Requests remaining\nRateLimit-Reset: 1640000000       // When limit resets (Unix timestamp)\nRetry-After: 120                  // Seconds until they can retry (on 429)\n\n// Example 429 response\n{\n  \"error\": \"Too many requests\",\n  \"retryAfter\": 120\n}\n",[90,140,138],{"__ignoreMap":92},[30,142,144],{"id":143},"best-practice-5-sliding-window-algorithm-3-min","Best Practice 5: Sliding Window Algorithm 3 min",[13,146,147],{},"Sliding windows are smoother than fixed windows:",[149,150,151,167],"table",{},[152,153,154],"thead",{},[155,156,157,161,164],"tr",{},[158,159,160],"th",{},"Algorithm",[158,162,163],{},"Pros",[158,165,166],{},"Cons",[168,169,170,182,193],"tbody",{},[155,171,172,176,179],{},[173,174,175],"td",{},"Fixed Window",[173,177,178],{},"Simple, low memory",[173,180,181],{},"Burst at window boundary",[155,183,184,187,190],{},[173,185,186],{},"Sliding Window",[173,188,189],{},"Smooth, no burst",[173,191,192],{},"More complex",[155,194,195,198,201],{},[173,196,197],{},"Token Bucket",[173,199,200],{},"Allows controlled bursts",[173,202,192],{},[30,204,206],{"id":205},"recommended-limits","Recommended Limits",[149,208,209,219],{},[152,210,211],{},[155,212,213,216],{},[158,214,215],{},"Endpoint Type",[158,217,218],{},"Recommended Limit",[168,220,221,229,237,245,253,261],{},[155,222,223,226],{},[173,224,225],{},"General API",[173,227,228],{},"100-1000/hour",[155,230,231,234],{},[173,232,233],{},"Login",[173,235,236],{},"5-10/15 minutes",[155,238,239,242],{},[173,240,241],{},"Password reset",[173,243,244],{},"3-5/hour",[155,246,247,250],{},[173,248,249],{},"Email sending",[173,251,252],{},"10/hour",[155,254,255,258],{},[173,256,257],{},"AI/expensive",[173,259,260],{},"10-50/hour",[155,262,263,266],{},[173,264,265],{},"Public read",[173,267,268],{},"1000+/hour",[270,271,272],"info-box",{},[13,273,274,277,278,285,286,291,292,297],{},[16,275,276],{},"Official Resources:"," For comprehensive rate limiting guidance, see ",[279,280,284],"a",{"href":281,"rel":282},"https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html",[283],"nofollow","OWASP Denial of Service Cheat Sheet",", ",[279,287,290],{"href":288,"rel":289},"https://cloud.google.com/architecture/rate-limiting-strategies-techniques",[283],"Google Cloud Rate Limiting Strategies",", and ",[279,293,296],{"href":294,"rel":295},"https://www.npmjs.com/package/express-rate-limit",[283],"express-rate-limit documentation",".",[299,300,301,308,314],"faq-section",{},[302,303,305],"faq-item",{"question":304},"Should I rate limit by IP or user ID?",[13,306,307],{},"Both. Use user ID for authenticated requests (prevents abuse from one account) and IP for unauthenticated requests (prevents brute force). Some attacks come from single IPs with multiple accounts.",[302,309,311],{"question":310},"How do I handle rate limiting behind a proxy?",[13,312,313],{},"Configure your app to trust the proxy and read the real IP from X-Forwarded-For header. In Express: app.set('trust proxy', 1). Be careful not to trust arbitrary headers.",[302,315,317],{"question":316},"Should I tell users when they are rate limited?",[13,318,319],{},"Yes, return a 429 status with a Retry-After header and a clear error message. This helps legitimate users and automated clients back off appropriately.",[30,321,323],{"id":322},"further-reading","Further Reading",[13,325,326],{},"Put these practices into action with our step-by-step guides.",[38,328,329,335,341],{},[41,330,331],{},[279,332,334],{"href":333},"/blog/how-to/add-security-headers","Add security headers to your app",[41,336,337],{},[279,338,340],{"href":339},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[41,342,343],{},[279,344,346],{"href":345},"/blog/getting-started/first-scan","Run your first security scan",[348,349,350,356,361],"related-articles",{},[351,352],"related-card",{"description":353,"href":354,"title":355},"Secure API design","/blog/best-practices/api-design","API Security",[351,357],{"description":358,"href":359,"title":360},"Login security","/blog/best-practices/authentication","Authentication",[351,362],{"description":363,"href":364,"title":365},"Detect abuse patterns","/blog/best-practices/monitoring","Monitoring",[367,368,371,375],"cta-box",{"href":369,"label":370},"/","Start Free Scan",[30,372,374],{"id":373},"check-your-rate-limiting","Check Your Rate Limiting",[13,376,377],{},"Scan your API for missing rate limits.",{"title":92,"searchDepth":379,"depth":379,"links":380},2,[381,382,383,384,385,386,387,388,389],{"id":32,"depth":379,"text":33},{"id":73,"depth":379,"text":74},{"id":95,"depth":379,"text":96},{"id":111,"depth":379,"text":112},{"id":127,"depth":379,"text":128},{"id":143,"depth":379,"text":144},{"id":205,"depth":379,"text":206},{"id":322,"depth":379,"text":323},{"id":373,"depth":379,"text":374},"best-practices","2026-01-29","Rate limiting security best practices. Learn to protect APIs from abuse, implement per-user limits, and choose the right rate limiting strategy for your application.",false,"md",[396,397,398],{"question":304,"answer":307},{"question":310,"answer":313},{"question":316,"answer":319},"vibe-green",null,{},true,"Protect your API from abuse with proper rate limiting implementation.","/blog/best-practices/rate-limiting","11 min read","[object Object]","Article",{"title":5,"description":392},{"loc":404},"blog/best-practices/rate-limiting",[],"summary_large_image","mOo9iVA7GmFJSoo8LokrOjWGPQSahNgy5Q0VXFG9OAQ",1775843925718]