Uma API sem rate limiting é uma porta aberta. Qualquer cliente pode fazer milhares de requests por segundo, derrubar o servidor, ou consumir recursos que outros usuários precisam. Mas rate limiting excessivo bloqueia usuários legítimos que estão usando a API normalmente.
Os algoritmos de rate limiting
Três abordagens dominam a prática:
Fixed Window
Conta requests dentro de uma janela fixa (ex: 100 requests por minuto). Simples, mas tem o problema do "edge burst": 100 requests no segundo 59 da janela + 100 no segundo 0 da próxima = 200 requests em 2 segundos.
Sliding Window Log
Mantém o timestamp de cada request na janela. Mais preciso, mas consome memória proporcional ao número de requests.
Token Bucket
O mais usado em produção. Tokens são adicionados em intervalos regulares. Cada request consome um token. Quando os tokens acabam, a request é rejeitada. Permite bursts controlados.
// Token bucket com Redis
async function isRateLimited(
key: string,
maxTokens: number,
refillRate: number
): Promise<boolean> {
const now = Date.now()
const bucket = await redis.hgetall(`ratelimit:${key}`)
if (!bucket.tokens) {
// Primeiro request: cria o bucket
await redis.hset(`ratelimit:${key}`, {
tokens: maxTokens - 1,
lastRefill: now
})
await redis.expire(`ratelimit:${key}`, 60)
return false
}
// Refill tokens baseado no tempo decorrido
const elapsed = now - Number(bucket.lastRefill)
const refill = Math.floor(elapsed / 1000 * refillRate)
const tokens = Math.min(maxTokens, Number(bucket.tokens) + refill)
if (tokens <= 0) return true // rate limited
await redis.hset(`ratelimit:${key}`, {
tokens: tokens - 1,
lastRefill: now
})
return false
}
Por chave: IP, usuário, ou API key
A chave de rate limit define quem está sendo limitado:
- Por IP: protege contra bots e abuso anônimo. Básico, mas não diferencia usuários legítimos de IPs compartilhados (CGNAT, VPNs).
- Por usuário autenticado: mais justo. Usuários pagam por plano, e o rate limit acompanha o plano.
- Por API key: para APIs públicas. Cada desenvolvedor tem seu bucket, e abuso não afeta outros.
- Composto: limite global por IP + limite individual por usuário. Protege contra DDoS e abuso simultaneamente.
Headers HTTP que comunicam o estado
Clientes precisam saber quando estão perto do limite. Estes headers são padrão de mercado:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 23
X-RateLimit-Reset: 1711234567
Retry-After: 30 // quando retorna 429
Retry-After é obrigatório em responses 429. Sem ele, o cliente não sabe quando tentar novamente e pode entrar em loop de retries.
Implementação com express-rate-limit
Para Node.js, a implementação padrão:
import rateLimit from 'express-rate-limit'
import RedisStore from 'rate-limit-redis'
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minuto
max: 100, // 100 requests por janela
standardHeaders: true,
legacyHeaders: false,
store: new RedisStore({
sendCommand: (...args) => redis.call(...args),
}),
keyGenerator: (req) => req.user?.id || req.ip,
handler: (req, res) => {
res.status(429).json({
error: {
code: 'RATE_LIMITED',
message: 'Muitas requisições. Tente novamente mais tarde.',
retryAfter: Math.ceil(req.rateLimit.resetTime / 1000)
}
})
}
})
app.use('/api/', limiter)
Rate limiting diferenciado por rota
Nem toda rota tem o mesmo custo. Login tem custo alto (bcrypt). Listagens são baratas. Aplique limites diferentes:
// Login: 5 tentativas por minuto (protege contra brute force)
app.use('/api/auth/login', rateLimit({ windowMs: 60000, max: 5 }))
// Listagens: 100 por minuto
app.use('/api/products', rateLimit({ windowMs: 60000, max: 100 }))
// Upload: 10 por hora
app.use('/api/upload', rateLimit({ windowMs: 3600000, max: 10 }))
O que fazer quando o rate limit é atingido
Além do 429 com Retry-After, implemente graceful degradation:缓存 a response por mais tempo, ou retorne dados parciais quando disponível. O usuário receives algo em vez de erro puro.
Curtiu o conteúdo?
Construo produtos web e soluções com IA do jeito certo — arquitetura sólida, código sustentável e entrega real.
Vamos conversar