Rate limiting och API-throttling: Skydda ditt API mot överbelastning
Implementera rate limiting med token bucket, sliding window och distribuerade lösningar. Skydda ditt API utan att straffa legitima användare.
Varför rate limiting?
Rate limiting skyddar ditt API mot överbelastning, missbruk och DDoS-attacker genom att begränsa antalet requests en klient kan göra under en tidsperiod. Utan rate limiting kan en enda felkonfigurerad klient eller en illvillig aktör sänka hela din tjänst.
Men rate limiting handlar inte bara om skydd - det handlar om rättvisa. Genom att fördela resurser jämnt säkerställer du att alla konsumenter får tillgång till API:et. En bra rate limiting-strategi kommunicerar gränserna tydligt via headers och ger meningsfulla felmeddelanden.
// Rate limit response headers (standard)
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1709654400
Retry-After: 30
{
"error": "rate_limit_exceeded",
"message": "Du har överskridit gränsen på 100 requests per minut",
"retryAfter": 30
}Token Bucket-algoritmen
Token Bucket är den mest populära rate limiting-algoritmen. Tänk dig en hink som fylls med tokens i jämn takt. Varje request förbrukar en token. Om hinken är tom nekas requesten. Fördelen är att den tillåter korta bursts av trafik (så länge det finns tokens) utan att straffa användare som gör enstaka requests.
För distribuerade system (flera serverinstanser) behöver token bucket-implementationen delas via Redis eller liknande. Annars får varje server sin egen räknare och det totala antalet tillåtna requests multipliceras.
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
interface RateLimitResult {
allowed: boolean;
remaining: number;
resetAt: number;
}
async function tokenBucketRateLimit(
key: string,
maxTokens: number,
refillRate: number // tokens per sekund
): Promise<RateLimitResult> {
const now = Date.now();
const bucketKey = "ratelimit:" + key;
// Atomär operation med Redis scripting
const tokens = await redis.hget(bucketKey, "tokens");
const lastRefill = await redis.hget(bucketKey, "lastRefill");
let currentTokens = tokens ? Number(tokens) : maxTokens;
const lastTime = lastRefill ? Number(lastRefill) : now;
// Fyll på tokens baserat på tid
const elapsed = (now - lastTime) / 1000;
currentTokens = Math.min(
maxTokens,
currentTokens + elapsed * refillRate
);
let allowed = false;
if (currentTokens >= 1) {
currentTokens -= 1;
allowed = true;
}
// Uppdatera Redis
await redis.hset(bucketKey, "tokens", currentTokens.toString());
await redis.hset(bucketKey, "lastRefill", now.toString());
await redis.expire(
bucketKey,
Math.ceil(maxTokens / refillRate) + 1
);
return {
allowed,
remaining: Math.floor(currentTokens),
resetAt:
now +
Math.ceil((maxTokens - currentTokens) / refillRate) * 1000,
};
}
// Middleware
export async function rateLimitMiddleware(req, res, next) {
const clientId = req.headers["x-api-key"] || req.ip;
const result = await tokenBucketRateLimit(clientId, 100, 1.67);
res.setHeader("X-RateLimit-Limit", 100);
res.setHeader("X-RateLimit-Remaining", result.remaining);
res.setHeader("X-RateLimit-Reset", Math.ceil(result.resetAt / 1000));
if (!result.allowed) {
return res.status(429).json({
error: "rate_limit_exceeded",
retryAfter: Math.ceil((result.resetAt - Date.now()) / 1000),
});
}
next();
}Tiered rate limiting
I praktiken behöver du ofta olika gränser för olika typer av användare. Gratisplaner har lägre gränser, betalande kunder får mer, och interna tjänster kan ha obegränsad åtkomst. Implementera detta med en konfiguration per API-nyckel eller användarnivå.
Kombinera gärna flera tidsfönster: 100 requests per minut OCH 5000 per timme. Detta skyddar mot både snabba bursts och långsamt men ihållande missbruk.
// Tiered rate limits per plan
const RATE_LIMITS = {
free: { perMinute: 20, perHour: 500, perDay: 5000 },
starter: { perMinute: 60, perHour: 2000, perDay: 20000 },
business: { perMinute: 200, perHour: 10000, perDay: 100000 },
enterprise: { perMinute: 1000, perHour: 50000, perDay: 500000 },
} as const;
async function tieredRateLimit(apiKey: string) {
const plan = await getPlanForApiKey(apiKey);
const limits = RATE_LIMITS[plan];
// Kontrollera alla tidsfönster
const results = await Promise.all([
checkLimit(apiKey + ":min", limits.perMinute, 60),
checkLimit(apiKey + ":hour", limits.perHour, 3600),
checkLimit(apiKey + ":day", limits.perDay, 86400),
]);
// Neka om något fönster är överskridet
const blocked = results.find((r) => !r.allowed);
if (blocked) {
return {
allowed: false,
plan,
...blocked,
};
}
return {
allowed: true,
plan,
remaining: Math.min(...results.map((r) => r.remaining)),
};
}Fler guider
Letar du efter pålitlig hosting för dina API-projekt?
Kolla in mehosting.com