How to prevent your API from collapsing under a flood of requests
\n\nImagine: you launched a cool service, wrote a backend in TypeScript, and then — bam! — one "kind" user runs a script that hammers your endpoint with thousands of requests per second. The server crashes, the database chokes, and honest users see a 502 error. Sound familiar?
\n\nIn the world of web development, rate limiting and throttling are not just buzzwords, but a lifeline for your API. Today we'll break down how to implement these mechanisms in TypeScript without racking your brain. Let's go!
\n\nRate limiting vs Throttling: what's the difference?
\n\nBefore writing code, let's clearly distinguish the concepts. Many people confuse them, but the difference is fundamental.
\n\n- \n
- Rate limiting is a hard limit: "No more than 100 requests per minute from one IP." Exceed it? You get a 429 Too Many Requests and a block until the end of the window. It works at the API gateway level. \n
- Throttling is smooth regulation: "You can make requests, but no more than once per second." Other requests are either queued or ignored, but the user is not blocked. \n
A simple analogy: rate limiting is a subway turnstile (swipe your card — pass, no — wait), while throttling is a traffic controller at an intersection (cars move, but with an interval).
\n\nWhen is this needed? Real-world scenarios
\n\nYou might say: "I have a small site, why do I need this?" Here's why:
\n\n- \n
- Protection against DDoS and brute force — attackers won't be able to brute-force passwords or take down the server. \n
- Resource savings — paid APIs (e.g., OpenAI) charge for each call. By limiting clients, you save your budget. \n
- Fair load distribution — one "noisy" user won't overwhelm everyone else. \n
- SLA compliance — if you promise 99.9% uptime, you can't do without restrictions. \n
Writing rate limiting in TypeScript: a simple start
\n\nLet's start with a basic example — an in-memory rate limiter based on a fixed window. It stores request counters directly in the application's memory.
\n\n// rateLimiter.ts\nimport { Request, Response, NextFunction } from 'express';\n\ninterface RateLimitEntry {\n count: number;\n resetTime: number;\n}\n\nclass InMemoryRateLimiter {\n private store: Map<string, RateLimitEntry> = new Map();\n private windowMs: number;\n private maxRequests: number;\n\n constructor(windowMs: number, maxRequests: number) {\n this.windowMs = windowMs;\n this.maxRequests = maxRequests;\n }\n\n public middleware() {\n return (req: Request, res: Response, next: NextFunction): void => {\n const key = req.ip || 'unknown';\n const now = Date.now();\n let entry = this.store.get(key);\n\n if (!entry || now > entry.resetTime) {\n // New window\n entry = { count: 0, resetTime: now + this.windowMs };\n this.store.set(key, entry);\n }\n\n entry.count++;\n\n if (entry.count > this.maxRequests) {\n res.status(429).json({\n error: 'Too Many Requests',\n retryAfter: Math.ceil((entry.resetTime - now) / 1000)\n });\n return;\n }\n\n // Pass limit information in headers\n res.setHeader('X-RateLimit-Limit', this.maxRequests);\n res.setHeader('X-RateLimit-Remaining', this.maxRequests - entry.count);\n res.setHeader('X-RateLimit-Reset', Math.ceil(entry.resetTime / 1000));\n\n next();\n };\n }\n\n // Cleanup old entries (to prevent memory leaks)\n public cleanup(): void {\