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.