Objetivos de aprendizado
Depois de ler este artigo, você vai conseguir:
- Entender a diferença entre janela fixa, janela deslizante e token bucket
- Aplicar rate limiting por IP numa API Express e devolver 429 com Retry-After
- Testar o limite com curl e tratar respostas 429 no cliente
Nesta página
- O que o rate limiting faz?
- Qual algoritmo de rate limiting usar?
- Como adicionar rate limiting numa API Express?
- Como testar se o limite funciona?
- O que uma resposta 429 deve conter?
- Por que o rate limiting quebra com várias instâncias?
- Rate limiting basta para barrar ataques?
- Como fazer isso na Square Cloud
- Perguntas frequentes
Toda API pública uma hora encontra um cliente que manda demais: um script preso num loop, um scraper ou alguém tentando adivinhar senhas. Sem limite, um único chamador consome a CPU e as conexões de banco de que todo mundo precisa.
O rate limiting é a proteção contra isso. A seguir você conhece os algoritmos mais usados, adiciona um limitador a uma API Node.js, testa o limite e vê como o cliente deve reagir a uma recusa. O texto supõe que você já conhece o básico de uma API REST.
O que o rate limiting faz?#
O rate limiter fica na frente dos handlers das rotas. Para cada requisição, ele escolhe uma chave (normalmente o IP, a chave de API ou o ID do usuário), incrementa um contador para essa chave e compara com o limite da janela atual. Abaixo do limite, a requisição segue. Acima dele, o servidor responde na hora com 429 Too Many Requests e nem executa o handler.
Ele protege você de três coisas:
- Abuso e força bruta: tentativas nas rotas de login, cadastro e redefinição de senha.
- Acidentes: um cliente com bug repetindo a chamada num loop apertado.
- Uso desigual: um usuário pesado deixando todo mundo lento, e contas inesperadas em rotas caras (consultas ao banco, chamadas de IA, envio de e-mail).
Rate limiting não é autenticação nem firewall. Ele controla com que frequência uma chave conhecida pode chamar, não quem tem permissão para chamar.
Qual algoritmo de rate limiting usar?#
| Algoritmo | Como funciona | Bom para | Cuidado com |
|---|---|---|---|
| Janela fixa | Conta as requisições por janela de calendário (por exemplo, por minuto) e zera na virada | Limites simples, o padrão da maioria das bibliotecas | Um cliente consegue mandar o limite duas vezes seguidas perto da virada (fim de uma janela mais o início da próxima) |
| Janela deslizante | Conta as requisições dos últimos N segundos, medidos a partir de agora | Limites suaves e justos | Mais memória e trabalho por requisição |
| Token bucket | Um balde se reabastece a uma taxa constante; cada requisição gasta uma ficha | APIs que permitem picos curtos mas limitam a média | Exige dois números: taxa de recarga e tamanho do balde |
| Limite de concorrência | Limita as requisições simultâneas em andamento | Rotas lentas ou caras | Não limita o volume total |
Para a maioria das APIs, a janela fixa basta para começar. Passe para janela deslizante ou token bucket quando o pico na virada da janela realmente atrapalhar.
Como adicionar rate limiting numa API Express?#
Você precisa de uma versão recente do Node.js e de uma aplicação Express. Os exemplos usam o express-rate-limit 8. Os cabeçalhos draft-7 usados abaixo exigem Express 4.11 ou posterior.
npm install express express-rate-limitCrie o server.mjs:
import express from "express";
import { rateLimit } from "express-rate-limit";
const app = express();
// General limit: 5 requests per minute per IP (low on purpose, to make testing easy)
const apiLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 5,
standardHeaders: "draft-7",
legacyHeaders: false,
message: { error: "too_many_requests" },
});
// Stricter limit for a sensitive route: 10 attempts per 15 minutes
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 10,
standardHeaders: "draft-7",
legacyHeaders: false,
});
app.use("/api", apiLimiter);
app.post("/api/login", loginLimiter, (req, res) => res.json({ ok: true }));
app.get("/api/hello", (req, res) => res.json({ hello: "world" }));
app.listen(process.env.PORT || 3000, "0.0.0.0");O que cada opção faz:
windowMsé a duração da janela em milissegundos. O padrão é 60000 (um minuto).limité o número máximo de requisições por chave na janela. O padrão é 5. O nome antigomaxainda funciona.standardHeadersenvia os cabeçalhos de respostaRateLimiteRateLimit-Policy. Vem desligado por padrão."draft-7"usa um único cabeçalhoRateLimitcombinado.legacyHeaders: falsedesliga os cabeçalhos antigosX-RateLimit-*, que vêm ligados por padrão.messageé o corpo da resposta 429. Pode ser uma string ou um objeto JSON.
Uma requisição para /api/login passa pelos dois limitadores, primeiro o geral e depois o mais rígido, então cada tentativa de login conta para os dois.
E o IP atrás de um proxy?#
Por padrão, a chave é o IP do cliente que o Express informa em req.ip. Atrás de um proxy reverso ou de um load balancer, esse é o endereço do proxy, a não ser que você diga ao Express para confiar no cabeçalho X-Forwarded-For. Sem isso, todos os usuários dividem um único contador e são bloqueados juntos.
app.set("trust proxy", 1); // one proxy in front of the appUm número indica quantos saltos de proxy são confiáveis. Evite true, a menos que o último proxy sobrescreva o X-Forwarded-For; senão, o cliente pode forjar o cabeçalho e escolher a própria chave. Confira o que o req.ip devolve depois do deploy e ajuste a configuração ao número real de proxies. Para entender o contexto, veja proxy reverso.
Como testar se o limite funciona?#
Suba o servidor e chame a mesma rota seis vezes:
PORT=3000 node server.mjs &
for i in 1 2 3 4 5 6; do curl -s -i http://localhost:3000/api/hello | grep -iE "^HTTP|ratelimit|retry|error"; doneAs cinco primeiras chamadas devolvem 200 OK e um cabeçalho RateLimit em contagem regressiva. A sexta devolve:
HTTP/1.1 429 Too Many Requests
RateLimit-Policy: 5;w=60
RateLimit: limit=5, remaining=0, reset=59
Retry-After: 59
{"error":"too_many_requests"}Depois de cerca de um minuto, o contador zera e a rota volta a responder 200. Se você nunca vê um 429, confira a ordem: o app.use("/api", apiLimiter) precisa vir antes das rotas que ele protege.
O que uma resposta 429 deve conter?#
- Status 429, definido na RFC 6585. Não use 403 nem 500.
Retry-After, que a RFC 9110 define como um número de segundos ou uma data HTTP. O servidor pode omiti-lo, mas o cliente depende dele para saber quando tentar de novo. Oexpress-rate-limitjá o define para você.- Um corpo legível por máquina, como
{"error":"too_many_requests"}, para o cliente não precisar interpretar texto. - Cabeçalhos
RateLimiteRateLimit-Policy, para que clientes bem-comportados diminuam o ritmo antes de bater no limite. Eles vêm de um rascunho da IETF ainda em andamento (draft-ietf-httpapi-ratelimit-headers), não de um padrão final, então o formato exato ainda pode mudar. A saídadraft-7mostrada acima segue uma revisão anterior do rascunho; as revisões mais novas escrevem os valores de outro jeito, então consulte a documentação da biblioteca antes de interpretar esses cabeçalhos. Os nomes antigosX-RateLimit-*são comuns, mas informais.
Nunca coloque segredos ou detalhes internos no corpo do erro.
Como o cliente deve tratar o 429?#
O cliente deve parar e esperar em vez de repetir na hora. Esta função respeita o Retry-After quando ele existe e, quando não existe, usa backoff exponencial:
async function fetchWithRetry(url, options = {}, maxRetries = 4) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429 || attempt >= maxRetries) return res;
const retryAfter = Number(res.headers.get("retry-after"));
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: 2 ** attempt * 500;
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
}Ela lê o Retry-After só como segundos. O cabeçalho também pode vir como data HTTP, então um cliente de produção deve tratar as duas formas.
Por que o rate limiting quebra com várias instâncias?#
O armazenamento padrão guarda os contadores na memória de um único processo Node.js. Isso traz dois problemas:
- Um restart zera todos os contadores. O cliente pode esperar um deploy e começar do zero.
- Várias instâncias contam separado. Com três instâncias e
limit: 100, um cliente consegue mandar até 300 requisições por janela, dependendo da instância que o load balancer escolhe. O mesmo acontece com o móduloclusterdo Node.
A solução é um armazenamento compartilhado. O Redis encaixa bem porque o INCR é atômico e o EXPIRE apaga os contadores antigos sozinho, que é o padrão descrito na documentação do Redis. Com o rate-limit-redis 6 e o cliente redis:
npm install rate-limit-redis redisimport { rateLimit } from "express-rate-limit";
import { RedisStore } from "rate-limit-redis";
import { createClient } from "redis";
const client = createClient({ url: process.env.REDIS_URL });
await client.connect();
const apiLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 60,
standardHeaders: "draft-7",
legacyHeaders: false,
store: new RedisStore({
sendCommand: (...args) => client.sendCommand(args),
}),
});Guarde a URL do Redis numa variável de ambiente, nunca no código. Se você roda uma única instância e aceita que os contadores zerem no restart, o armazenamento em memória serve.
Rate limiting basta para barrar ataques?#
Não. Um limitador dentro da aplicação só protege as camadas que estão atrás dele. Cada requisição recusada ainda custa uma conexão, o parsing e a execução de um middleware. Contra inundações volumétricas, você precisa filtrar o tráfego antes que ele chegue ao seu código, que é o papel da proteção contra DDoS. Mantenha o limitador da aplicação para garantir uso justo e conter abusos em rotas específicas, como o login, e revise-o junto com o resto do checklist de API em produção.
Como fazer isso na Square Cloud#
A Square Cloud é hospedagem self-service: você faz o deploy da API e escreve o limitador, como mostrado aqui. O código acima roda no runtime Node.js, num container isolado sempre ligado, sem cold start nem modo de suspensão. Os contadores em memória ainda zeram sempre que o processo reinicia. Aplicações web começam em 512 MB de RAM, e o painel permite definir variáveis de ambiente por aplicação, como a REDIS_URL.
Todo plano já inclui proteção DDoS (L3, L4 e L7) e um WAF na frente da sua aplicação, então o limitador no seu código pode se concentrar em limites por rota e por chave. Se você roda mais de uma instância e precisa de contadores compartilhados, a hospedagem de Redis está disponível a partir do plano Standard, no mesmo datacenter das suas aplicações.
Veja os detalhes e os planos em hospedagem de APIs na Square Cloud.
Hospedagem de APIs
APIs em containers sempre ligados, sem cold start
Perguntas frequentes
Qual código HTTP indica rate limit?
429 Too Many Requests, definido na RFC 6585. O servidor pode incluir o cabeçalho Retry-After dizendo quanto tempo o cliente deve esperar antes de tentar de novo.
Qual é um bom limite de requisições para uma API?
Não existe número universal. Parta do que um cliente normal precisa (por exemplo, 60 requisições por minuto numa rota pública de leitura) e defina limites bem mais rígidos nas rotas de login, cadastro e redefinição de senha.
Rate limiting impede ataque DDoS?
Só os pequenos. Um limitador dentro da aplicação ainda gasta CPU e memória com cada requisição recusada, então inundações grandes precisam ser absorvidas antes, na borda da rede, sem chegar ao seu código.
Devo limitar por endereço IP ou por chave de API?
Use a chave de API ou o ID do usuário quando a requisição é autenticada, e o IP nas rotas anônimas. Limite por IP pode bloquear muita gente atrás de um mesmo endereço, como um escritório ou uma operadora de celular.
Por que meu rate limiter não funciona com várias instâncias?
O contador padrão em memória vive dentro de um processo só. Com várias instâncias ou depois de um restart, cada uma conta separado, então você precisa de um armazenamento compartilhado, como o Redis.
Mais em APIs
Ver todos os 15 artigos- Como hospedar uma aplicação Node.jsPara hospedar uma aplicação Node.js, prepare o package.json, leia porta e segredos do ambiente, escolha uma versão LTS e rode com node. Veja o passo a passo.
- Como hospedar FastAPI, Flask ou DjangoPara hospedar FastAPI, Flask ou Django, rode a aplicação com um servidor de produção (Uvicorn ou Gunicorn) em 0.0.0.0. Veja comandos, ajustes do Django e erros.
- Como hospedar um script ou API em PythonPara hospedar um script ou API em Python, fixe a versão, isole dependências em um venv, leia segredos do ambiente e trate o SIGTERM. Passo a passo com código.
- O que é cron job e como agendar tarefasCron job é uma tarefa que roda sozinha em horários definidos por uma expressão de 5 campos. Veja a sintaxe, exemplos, erros comuns e como agendar em Node.js.