Rate limiting por usuário no NestJS com Guard + Redis (sliding window)
Por John Pires · 19 de março de 2026

Rate limiting por usuário no NestJS com Guard + Redis (sliding window)
Por que aprender isso em 2026
Contexto do problema
Rate limiting é a técnica de controlar a quantidade de requisições que um cliente pode fazer a um serviço em um intervalo de tempo. Em 2026, aplicações web e APIs continuam crescendo em escala e distribuição: sistemas serverless, microservices e clientes móveis competem por recursos. Ataques simples de negação de serviço, uso indevido de APIs públicas e picos inesperados de tráfego seguem sendo problemas reais. Controlar o consumo por usuário evita abuso, garante experiência e reduz custos.
Por que usar sliding window e Redis
Existem várias estratégias de rate limiting: fixed window, token bucket, leaky bucket e sliding window. O fixed window é simples, porém gera burst indesejado no começo de cada window. Sliding window suaviza esse efeito, contabilizando eventos por timestamps e permitindo limites mais estáveis. Redis é a escolha natural para aplicar políticas distribuídas: é rápido, tem estruturas (sorted sets) úteis e suporta scripts atômicos (Lua), essenciais para consistência entre múltiplas instâncias do serviço.
Por que implementar no NestJS com Guard
NestJS é um framework Node.js baseado em TypeScript que organiza código com controllers, providers e guards. Guards são ideais para rate limiting porque executam antes do handler: permitem recusar a requisição cedo. Implementar a lógica no Guard mantém controllers limpos e permite aplicar regras por rota, por controlador ou globalmente. Integrar Guard + Redis dá solução escalável e reutilizável.
Use sliding window quando precisar de limites por usuário precisos e comportamentos suaves em picos; combine com Redis e scripts para garantir atomicidade e desempenho.
Configuração do ambiente
Requisitos mínimos
Para seguir os exemplos você precisa de:
- Node.js 18+
- npm ou yarn
- NestJS CLI (opcional, mas recomendado)
- Redis (local via Docker ou remoto)
Instale dependências essenciais no projeto Nest:
npm init -y
npm i @nestjs/core @nestjs/common @nestjs/platform-express reflect-metadata rxjs
npm i ioredis
npm i --save-dev typescript ts-node-dev @types/node
O código acima cria um projeto básico e instala o ioredis, cliente Redis recomendado.
Rodando Redis localmente
Se você tem Docker:
docker run -d --name redis -p 6379:6379 redis:7-alpine
Isso expõe Redis na porta padrão 6379. Em produção use provisionamento adequado e autenticação.
Estrutura mínima do projeto
Crie uma estrutura simples:
- src/main.ts
- src/app.module.ts
- src/redis/redis.module.ts
- src/guards/rate-limit.guard.ts
- src/controllers/sample.controller.ts
O módulo Redis encapsula o cliente, o Guard aplica a lógica e o controller é o ponto de teste das regras.
Hello World progressivo
1) App básico NestJS
Primeiro, um main mínimo para levantar o servidor:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
Esse arquivo inicia a aplicação Nest. Depois de rodar, você terá uma HTTP API vazia escutando em 3000.
2) Módulo Redis (provider)
Vamos encapsular o cliente Redis com ioredis para injetar em Guards ou outros serviços.
// src/redis/redis.module.ts
import { Global, Module } from '@nestjs/common';
import Redis from 'ioredis';
const redisProvider = {
provide: 'REDIS',
useFactory: () => {
return new Redis({ host: '127.0.0.1', port: 6379 });
},
};
@Global()
@Module({ providers: [redisProvider], exports: [redisProvider] })
export class RedisModule {}
Isso cria um provider global chamado 'REDIS'. Injetando 'REDIS' em qualquer lugar você terá o cliente ioredis pronto.
3) Controller de teste
Um controller simples para testar comportamento antes de aplicar o guard:
// src/controllers/sample.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller('sample')
export class SampleController {
@Get()
get() {
return { ok: true, time: Date.now() };
}
}
// Depois adicione SampleController no AppModule
Chamar GET /sample retorna um JSON simples. Próximo passo: aplicar rate limit.
Mini-projeto prático
Estratégia: sliding window com sorted sets
Implementaremos sliding window por usuário usando sorted sets (zset). Ideia:
- Para cada requisição, insere-se um score igual ao timestamp em milissegundos no zset com key user:{id}:rl
- Remove entradas mais antigas que a janela (timestamp < now - window)
- Conta quantos elementos permanecem; se maior que limite, bloqueia
- Define um TTL curto na chave para limpeza automática
Essa sequência precisa ser atômica; usaremos um script Lua executado por EVAL para garantir atomicidade entre múltiplas instâncias.
Guard: implementação com Lua para atomicidade
O Guard verifica se a requisição deve ser permitida. Exemplo de Guard com chamada ao Redis e uso de script Lua:
// src/guards/rate-limit.guard.ts
import { CanActivate, ExecutionContext, Injectable, Inject, ForbiddenException } from '@nestjs/common';
import { Request } from 'express';
import Redis from 'ioredis';
@Injectable()
export class RateLimitGuard implements CanActivate {
constructor(@Inject('REDIS') private readonly redis: Redis) {}
// Limite: 5 requisições por 10 segundos (exemplo para iniciantes)
private readonly limit = 5;
private readonly windowMs = 10_000;
async canActivate(context: ExecutionContext): Promise<boolean> {
const req: Request = context.switchToHttp().getRequest();
// Identifique o usuário: se não autenticado, use IP
const userId = (req.user && (req.user as any).id) || req.ip;
const key = `rl:${userId}`;
const now = Date.now();
// Script Lua: insere, remove antigos e conta
const lua = `
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local member = ARGV[4]
redis.call('ZADD', key, now, member)
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
redis.call('PEXPIRE', key, window)
return count
`;
const member = `${now}-${Math.random().toString(36).slice(2)}`;
const count = await this.redis.eval(lua, 1, key, now, this.windowMs, this.limit, member);
if (count > this.limit) {
throw new ForbiddenException('Rate limit exceeded');
}
return true;
}
}
O script Lua realiza os passos atômicos: ZADD, ZREMRANGEBYSCORE, ZCARD e PEXPIRE. Isso previne condições de corrida entre instâncias concorrentes.
Como usar o Guard no controller
Exemplo de aplicação do Guard em uma rota específica:
// src/controllers/sample.controller.ts (com guard)
import { Controller, Get, UseGuards } from '@nestjs/common';
import { RateLimitGuard } from '../guards/rate-limit.guard';
@Controller('sample')
export class SampleController {
@Get()
@UseGuards(RateLimitGuard)
get() {
return { ok: true, time: Date.now() };
}
}
// Lembre-se de exportar/registrar RateLimitGuard no AppModule como provider
Agora, cada chamada a GET /sample passa pelo guard que aplica sliding window. Se exceder 5 requisições em 10s por usuário/ip, será retornado 403.
Alternativas e melhorias
Possíveis aprimoramentos:
- Token bucket para permitir bursts controlados
- Separar limites por rota/role (usuário gratuito x premium)
- Expor headers X-RateLimit-* para feedback ao cliente
- Usar Redis cluster ou cliente com suporte a cluster se necessário
Também é possível usar Interceptor para adicionar headers ou balancear comportamento após o handler, mas Guard é preferível para negar antes do processamento.
Script Lua explicado passo a passo
O script Lua utiliza argumentos para ser genérico. Fluxo:
- ZADD adiciona um membro com score timestamp
- ZREMRANGEBYSCORE remove todos com score menor que now - window
- ZCARD conta elementos restantes
- PEXPIRE define TTL para limpeza automática
Retornamos o count para o guard decidir se aceita ou rejeita a requisição. A escolha do member como combinação random+timestamp evita colisões idênticas.
Erros comuns, armadilhas e dicas práticas
Erro: usar get/set em vez de script
Muitos iniciantes fazem: MULTI { get count; if count < limit set count+1; exec }. Sem script, entre get e set outra instância pode inserir, causando inconsistência. Use scripts Lua ou comandos Redis atômicos sempre que múltiplas operações precisam ser atômicas.
Armadilha: relógio do servidor
O sliding window usa timestamps do servidor que executa o Guard. Em ambientes distribuídos, clocks desalinhados podem produzir comportamentos estranhos. Soluções:
- Use Date.now() no servidor da aplicação; garanta NTP sincronizado em todas as instâncias
- Ou use REdis time (TIME) como fonte de verdade, mas isso adiciona uma chamada extra
Dica: feedback para o cliente
Adicionar headers ajuda o cliente a respeitar limites: X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset. Use um interceptor para injetar esses headers baseados no resultado do script.
Erro: escolher window/limit inadequados
Parâmetros ruins causam falsos positivos. Teste com tráfego real e métricas. Diferencie limites para endpoints sensíveis e públicos.
Recursos para continuar estudando
Documentação e leituras técnicas
Referências essenciais:
- Redis docs: sorted sets, Lua scripting, EXPIRE
- NestJS docs: Guards, Modules, Interceptors
- Artigos sobre algoritmos de ratelimit: token bucket vs leaky vs sliding window
Ler a documentação oficial do Redis sobre scripts ajuda a entender limites de performance e segurança.
Exercícios práticos
Exercícios recomendados para fixar:
- Adicionar headers com interceptor indicando restante e reset
- Implementar limites diferentes por rota usando decorators
- Substituir member simples por hash de user + requestId para auditoria
Esses exercícios transformam compreensão teórica em código reutilizável e testável.
Conclusão prática
Resumo das decisões
Implementar rate limiting por usuário em NestJS com Guard + Redis usando sliding window oferece balanceamento entre precisão e suavidade no controle de tráfego. O uso de Lua scripts garante atomicidade necessária em ambientes distribuídos. Guards permitem negar requisições cedo, preservando recursos.
Checklist de implantação
Antes de rodar em produção, verifique:
- Sincronização de relógio entre instâncias
- Configuração adequada do Redis (replicação, autenticação)
- Métricas e logs para monitorar rejeições
- Testes de carga para validar parâmetros
Pronto: você tem um Guard funcional com Redis que aplica sliding window por usuário. O próximo passo é adaptar limites por rota, adicionar headers de feedback e integrar com métricas para observabilidade.
Observação final: para projetos que exigem controle extremamente fino ou garantias adicionais (por exemplo, billing preciso), complemente essa abordagem com armazenamento de eventos ou contadores em banco persistente e revisões arquiteturais específicas.
Assuntos: NestJS, guards, NoSQL, rate-limiting, sliding-window
Leia também
Testando Guards e Interceptors no NestJS com Jest e SuperTest — do jeito errado ao certo
Guards controlam acesso e Interceptors transformam respostas. Aprenda a testar ambos no NestJS: mostro o jeito errado, corrijo e explico testes unitários e…
API REST com NestJS, Prisma e PostgreSQL: JWT, testes e otimização de performance
Guia avançado para APIs com NestJS, Prisma e PostgreSQL. Cobre autenticação JWT, testes automáticos (unit/e2e) e otimizações de performance para produção…
Autenticação JWT segura no NestJS com refresh tokens rotativos e Redis
Implementação prática de autenticação JWT no NestJS com refresh tokens rotativos, armazenamento de jti em Redis e detecção de replay. Guia com exemplos de…
Limitador de requisições no NestJS com Throttler + Redis: passo a passo para iniciantes
Guia prático para iniciantes: configure rate limiting no NestJS usando @nestjs/throttler e Redis. Passo a passo com exemplos funcionais, Docker, dicas de…
Autorização por permissões no NestJS usando decorators e guards
Aprenda a controlar autorização por permissões no NestJS usando decorators e guards. Tutorial prático, passo a passo, com exemplos funcionais, integração JWT…
NestJS: criar e testar microservice TCP e gateway HTTP passo a passo
Passo a passo prático para criar e testar um microservice TCP em NestJS e um gateway HTTP. Mostro o jeito errado, o jeito certo, exemplos funcionais e como…