
Rate limiting: como proteger sua API em produção
Imagine que você lançou sua API e, na primeira semana, um único cliente com um bug no código de integração começa a fazer 10.000 requisições por minuto. Sem rate limiting, sua API cai para todos os outros clientes. Com rate limiting, aquele cliente recebe respostas 429 Too Many Requests, o bug dele fica visível nos logs e os demais usuários nem percebem o problema.
Rate limiting não é apenas proteção contra ataques DDoS — é garantia de disponibilidade e fairness entre os consumidores da sua API. É uma das primeiras funcionalidades que deveria ser implementada em qualquer API que vai para produção.
Algoritmos: Token Bucket vs Leaky Bucket vs Fixed Window
A escolha do algoritmo define o comportamento da sua API sob pressão. Cada um tem trade-offs diferentes.
Fixed Window (Janela Fixa)
O mais simples: conta requisições em janelas de tempo fixas (por minuto, por hora). "Máximo 100 requisições por minuto."
Problema: vulnerável ao ataque de "borda de janela". Um cliente pode fazer 100 requisições aos 59 segundos e mais 100 requisições ao segundo seguinte — 200 requisições em 2 segundos, sem violar a regra formalmente.
Sliding Window (Janela Deslizante)
Resolve o problema da janela fixa: em vez de resetar o contador em intervalos fixos, a janela "desliza" com o tempo. A contagem inclui todas as requisições nos últimos 60 segundos, independente de quando a janela começou.
Mais preciso, mas requer mais memória no Redis para armazenar os timestamps individuais.
Token Bucket
Cada cliente tem um "balde" de tokens. Cada requisição consome um token. O balde é reabastecido a uma taxa constante (ex.: 10 tokens por segundo, máximo 100 tokens). Se o balde está vazio, a requisição é rejeitada.
Vantagem: permite bursts controlados. Um cliente que ficou inativo acumula tokens e pode fazer um burst legítimo. Isso é ideal para APIs onde picos de uso são esperados (ex.: um usuário que abre o app e carrega vários dados de uma vez).
Leaky Bucket
O inverso do token bucket: requisições entram no balde e saem em taxa constante, como água vazando por um buraco. Se o balde enche, requisições são descartadas.
Vantagem: taxa de saída absolutamente constante, ideal para proteger serviços downstream que não toleram variação de carga. Desvantagem: penaliza bursts legítimos.
| Algoritmo | Burst permitido | Complexidade | Uso ideal |
|---|---|---|---|
| Fixed Window | Sim (borda) | Baixa | Prototipagem, sistemas internos |
| Sliding Window | Não | Média | APIs públicas com fairness rigorosa |
| Token Bucket | Sim (controlado) | Média | APIs de produto, mobile backends |
| Leaky Bucket | Não | Média | Proteção de serviços downstream |
Implementação com Redis e express-rate-limit
Para APIs Node.js, a combinação express-rate-limit + rate-limit-redis é o padrão de mercado. Redis garante que o contador seja compartilhado entre todas as instâncias da API (essencial em produção com múltiplos pods).
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
import { createClient } from "redis";
const redisClient = createClient({
url: process.env.REDIS_URL,
});
await redisClient.connect();
// Rate limiter global: 100 req/min por IP
export const globalLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minuto
max: 100,
standardHeaders: true, // Retorna headers RateLimit-*
legacyHeaders: false,
store: new RedisStore({
sendCommand: (...args) => redisClient.sendCommand(args),
}),
handler: (req, res) => {
res.status(429).json({
error: "Too Many Requests",
message: "Você excedeu o limite de requisições. Tente novamente em breve.",
retryAfter: res.getHeader("Retry-After"),
});
},
});
// Rate limiter mais restrito para endpoints sensíveis
export const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 10, // Máximo 10 tentativas de login em 15 minutos
skipSuccessfulRequests: true, // Não conta tentativas bem-sucedidas
store: new RedisStore({
sendCommand: (...args) => redisClient.sendCommand(args),
}),
});
// Aplicando os limiters
app.use("/api/", globalLimiter);
app.use("/api/auth/login", authLimiter);
app.use("/api/auth/forgot-password", authLimiter);
Para Next.js App Router, o padrão é ligeiramente diferente mas o conceito é idêntico — a lógica de rate limiting vai no middleware ou no início de cada route handler.
Estratégias: Por IP, Por API Key e Por Usuário
A granularidade do rate limiting define a eficácia da proteção. As três estratégias têm casos de uso distintos.
Por IP: A estratégia mais simples e a primeira linha de defesa. Funciona bem para APIs públicas sem autenticação. Problema: IPs compartilhados (empresas atrás de NAT, redes de faculdade) penalizam usuários legítimos. Proxies e VPNs contornam facilmente.
Por API Key: A estratégia certa para APIs com autenticação baseada em chaves. Cada cliente tem sua própria quota, independente do IP. Você consegue oferecer tiers diferentes (free: 1.000 req/dia, pro: 100.000 req/dia) e identificar exatamente qual cliente está abusando.
Por Usuário Autenticado: Para APIs com login (JWT, session), use o userId como chave. Isso garante que um usuário não abuse da API independente do IP ou dispositivo que usa.
Estratégia recomendada: combine as três em camadas. Rate limit por IP como primeira defesa (sem consultar banco), depois por API Key/usuário para limites personalizados por tier.
// Chave dinâmica: prioriza user ID > API Key > IP
const keyGenerator = (req: Request): string => {
if (req.user?.id) return `user:${req.user.id}`;
if (req.headers["x-api-key"]) return `key:${req.headers["x-api-key"]}`;
return `ip:${req.ip}`;
};
Respostas 429: Headers Retry-After e Mensagens Úteis
A qualidade da resposta 429 determina se seu rate limiting é "developer-friendly" ou frustrante. Uma boa resposta 429 inclui:
Headers padrão (RFC 6585 + RateLimit Headers):
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 1720000060
Retry-After: 47
Retry-After: segundos até que o cliente possa tentar novamente. Permite que SDKs implementem retry automático.RateLimit-Remaining: quantas requisições o cliente ainda tem na janela atual. Permite que clientes bem-comportados se auto-regulem antes de atingir o limite.
Body da resposta: inclua informações acionáveis. "Rate limit exceeded" não ajuda. "Você atingiu o limite de 100 requisições por minuto. Próxima janela disponível em 47 segundos." ajuda muito.
Para APIs com tiers de plano, inclua um link para upgrade: "upgradeUrl": "https://api.meusite.com.br/pricing".
Conclusão
Rate limiting é uma das funcionalidades mais fáceis de implementar e com maior retorno em resiliência e segurança. A receita é simples: Redis como store compartilhado, algoritmo Token Bucket para a maioria dos casos, granularidade por usuário/API Key em vez de apenas por IP, e respostas 429 com Retry-After que permitem clientes inteligentes se comportar bem automaticamente.
O que diferencia APIs de produção de protótipos não é apenas a funcionalidade — é a infraestrutura de proteção ao redor dela. No SystemForge, rate limiting, autenticação e versionamento são especificados na documentação antes do desenvolvimento começar, garantindo que a API chegue em produção pronta para o mundo real. Se você está construindo uma API que precisa aguentar pressão, podemos ajudar a estruturar isso do jeito certo.
Precisa de API e Integrações?
Desenvolvemos APIs robustas e integramos com qualquer sistema.
Saiba mais →Precisa de ajuda?
