Aplicações Node.js costumam começar simples: uma API, algumas rotas e um banco de dados. Conforme o número de usuários aumenta, surgem novos desafios. Consultas repetidas deixam a resposta lenta, tarefas pesadas bloqueiam requisições e diferentes instâncias da aplicação precisam compartilhar informações temporárias. É nesse cenário que o Redis com Node.js se torna uma ferramenta muito útil.
Neste guia, você vai aprender como instalar o Redis, conectar uma aplicação Node.js, criar cache com expiração, invalidar dados, usar listas como filas simples e aplicar cuidados importantes em produção. O objetivo é mostrar um fluxo prático sem esconder os pontos que mais causam erros, como chaves desatualizadas, excesso de memória e operações que nunca expiram.
O que é Redis?
Redis é um armazenamento de dados em memória. Ele trabalha com estruturas como strings, hashes, listas, conjuntos e conjuntos ordenados. Como os dados principais ficam na memória RAM, muitas operações são executadas com latência muito baixa.
Isso não significa que Redis substitui automaticamente um banco relacional. Em muitos projetos, ele funciona ao lado do banco principal. O PostgreSQL ou MySQL continua armazenando os dados permanentes, enquanto o Redis guarda resultados temporários, sessões, contadores, bloqueios e mensagens de fila.
Para revisar conceitos gerais, consulte o guia sobre o que é banco de dados. Se você ainda está conhecendo a plataforma usada neste tutorial, veja também o que é Node.js.
Quando usar Redis com Node.js?
Redis é especialmente útil quando a aplicação precisa acessar rapidamente informações pequenas e temporárias. Alguns casos comuns são:
- cache de respostas de APIs;
- armazenamento de sessões;
- limitação de requisições por usuário ou IP;
- contadores em tempo real;
- filas de tarefas;
- publicação e assinatura de eventos;
- bloqueios distribuídos;
- placares, rankings e métricas rápidas.
Evite adicionar Redis apenas porque ele é rápido. Cada nova tecnologia aumenta a complexidade operacional. Use-o quando houver um problema mensurável, como consultas repetitivas, necessidade de compartilhar estado temporário ou execução assíncrona de tarefas.
Instalando o Redis
Você pode instalar o Redis diretamente no sistema operacional, usar um serviço gerenciado ou executar um contêiner. Para desenvolvimento local, Docker é uma opção prática:
docker run --name redis-local -p 6379:6379 -d redis:7O comando cria um contêiner e expõe a porta padrão 6379. Para confirmar que o serviço está respondendo, execute:
docker exec -it redis-local redis-cli pingA resposta esperada é PONG. Caso ainda não conheça contêineres, leia o artigo sobre o que é Docker.
Preparando o projeto Node.js
Crie uma pasta e instale as dependências:
mkdir redis-node
cd redis-node
npm init -y
npm install redis expressAdicione "type": "module" ao arquivo package.json para usar módulos ES. Depois, crie src/redis.js:
import { createClient } from "redis";
export const redis = createClient({
url: process.env.REDIS_URL ?? "redis://localhost:6379"
});
redis.on("error", (error) => {
console.error("Erro no Redis:", error);
});
export async function connectRedis() {
if (!redis.isOpen) {
await redis.connect();
}
}A variável REDIS_URL permite usar uma configuração diferente em produção. Nunca coloque senha, host privado ou token diretamente no código. A documentação oficial do cliente Node.js do Redis apresenta as opções atuais de conexão e exemplos de comandos.
Criando uma API de exemplo
Agora crie src/server.js:
import express from "express";
import { connectRedis, redis } from "./redis.js";
const app = express();
app.use(express.json());
await connectRedis();
app.get("/health", async (req, res) => {
const pong = await redis.ping();
res.json({ redis: pong });
});
app.listen(3000, () => {
console.log("API em http://localhost:3000");
});Esse endpoint verifica se a aplicação consegue conversar com o Redis. Em um projeto real, o health check pode também testar o banco principal e outros serviços essenciais. O tutorial sobre como criar uma API com Node.js ajuda a organizar a estrutura inicial.
Salvando e lendo valores
O tipo mais simples é uma string. Para salvar:
await redis.set("site:status", "online");Para ler:
const status = await redis.get("site:status");Use nomes de chaves consistentes. Prefixos como user:, product: e cache: ajudam a identificar a finalidade dos dados. Uma chave poderia ser cache:product:42.
Criando cache com expiração
Imagine uma rota que consulta um produto no banco. Podemos verificar o cache antes de executar a consulta:
app.get("/products/:id", async (req, res) => {
const id = req.params.id;
const key = `cache:product:${id}`;
const cached = await redis.get(key);
if (cached) {
return res.json({
source: "cache",
data: JSON.parse(cached)
});
}
const product = await findProductInDatabase(id);
if (!product) {
return res.status(404).json({ message: "Produto não encontrado" });
}
await redis.set(key, JSON.stringify(product), {
EX: 300
});
return res.json({
source: "database",
data: product
});
});A opção EX: 300 define uma expiração de cinco minutos. Sem expiração, o cache pode permanecer para sempre e ocupar memória desnecessariamente. O tempo ideal depende da frequência de atualização e da tolerância a dados temporariamente antigos.
O padrão cache-aside
O exemplo usa o padrão cache-aside. A aplicação tenta ler do cache. Quando não encontra, consulta o banco, devolve a resposta e salva uma cópia temporária no Redis.
Esse padrão é simples e flexível, mas exige cuidado com invalidação. Se o produto for atualizado no banco, a chave antiga ainda pode continuar disponível até expirar. Para reduzir esse problema, remova ou atualize o cache após a operação de escrita.
app.put("/products/:id", async (req, res) => {
const product = await updateProductInDatabase(
req.params.id,
req.body
);
await redis.del(`cache:product:${req.params.id}`);
res.json(product);
});Apagar a chave força a próxima leitura a buscar o valor atualizado no banco. Em sistemas com várias chaves derivadas do mesmo registro, documente quais caches precisam ser invalidados.
Evitando o cache stampede
Quando uma chave popular expira, centenas de requisições podem consultar o banco ao mesmo tempo. Esse efeito é conhecido como cache stampede. Algumas estratégias para reduzir o problema são:
- adicionar uma pequena variação aleatória ao tempo de expiração;
- usar bloqueio temporário para permitir apenas uma reconstrução;
- renovar chaves populares antes da expiração;
- limitar concorrência nas consultas mais pesadas.
Não existe uma técnica universal. Para aplicações pequenas, uma expiração simples pode ser suficiente. Em sistemas com grande tráfego, teste o comportamento sob concorrência.
Usando hashes
Hashes armazenam vários campos dentro da mesma chave:
await redis.hSet("user:100", {
name: "Ana",
email: "ana@example.com",
plan: "pro"
});
const user = await redis.hGetAll("user:100");Hashes são úteis quando você precisa ler ou atualizar campos individuais. Entretanto, defina uma estratégia de expiração para a chave inteira e não use Redis como repositório permanente sem avaliar persistência, backup e consistência.
Criando uma fila simples com listas
Listas permitem inserir elementos em uma extremidade e retirar da outra. Isso pode formar uma fila:
await redis.lPush("queue:emails", JSON.stringify({
to: "cliente@example.com",
template: "welcome"
}));Um worker pode aguardar novas tarefas:
while (true) {
const result = await redis.brPop("queue:emails", 0);
if (!result) continue;
const job = JSON.parse(result.element);
await sendEmail(job);
}BRPOP bloqueia a conexão até surgir um item. Para tarefas importantes, uma lista simples pode não ser suficiente. Você precisará pensar em confirmação, repetição, mensagens com erro, atraso e observabilidade. Bibliotecas de filas baseadas em Redis oferecem recursos prontos para esses casos.
Quando usar uma biblioteca de filas?
Use uma biblioteca especializada quando as tarefas precisarem de:
- tentativas automáticas;
- atrasos e agendamento;
- prioridades;
- limite de concorrência;
- histórico de falhas;
- painel de monitoramento;
- remoção e retenção configuráveis.
A fila manual é ótima para compreender o conceito, mas sistemas críticos precisam garantir que uma tarefa não desapareça silenciosamente após uma falha.
Contadores e expiração
Redis também é útil para contadores:
const key = `login:attempts:${userId}`;
const attempts = await redis.incr(key);
if (attempts === 1) {
await redis.expire(key, 900);
}
if (attempts > 5) {
throw new Error("Muitas tentativas");
}Esse exemplo conta tentativas durante quinze minutos. Em produção, combine esse controle com autenticação segura, logs e regras que evitem bloquear usuários legítimos indevidamente.
Tratando desconexões
A rede pode falhar e o servidor Redis pode reiniciar. Não presuma que toda chamada sempre funcionará. Trate erros nas operações importantes e decida se a aplicação pode continuar sem cache.
Em muitos casos, uma falha no cache deve levar a aplicação a consultar o banco, e não a interromper completamente a resposta. Já uma fila de pagamentos pode exigir comportamento mais conservador. Classifique o Redis como dependência opcional ou crítica para cada funcionalidade.
Segurança e produção
Não exponha a porta do Redis diretamente à internet. Use rede privada, autenticação, TLS quando disponível e regras de firewall. Em serviços gerenciados, restrinja o acesso às aplicações autorizadas.
Também monitore memória, número de conexões, taxa de acertos do cache, evicções e latência. Configure uma política de remoção adequada ao uso. A documentação oficial sobre otimização de memória explica estruturas, consumo e estratégias de configuração.
Boas práticas
- Defina expiração: todo cache deve ter uma política de validade.
- Padronize chaves: use prefixos e identificadores previsíveis.
- Meça antes de otimizar: confirme que o cache resolve um gargalo real.
- Não armazene dados sensíveis sem proteção: Redis não elimina requisitos de segurança.
- Planeje invalidação: documente quando cada chave precisa ser removida.
- Monitore taxa de acerto: cache que quase nunca é usado pode apenas consumir recursos.
- Evite objetos enormes: valores grandes aumentam memória, rede e tempo de serialização.
- Teste falhas: simule indisponibilidade e confirme o comportamento da aplicação.
Deploy e observabilidade
No deploy, nunca execute Redis dentro do mesmo processo da aplicação. Use um serviço separado e configure a URL por variável de ambiente. Em pipelines, valide que o código não depende de um Redis local fixo. Para automatizar o processo, consulte o guia de deploy com GitHub Actions.
Registre erros de conexão, tempo das operações e quantidade de falhas. Evite salvar valores sensíveis nos logs. Dashboards devem mostrar o consumo de memória e a estabilidade da instância ao longo do tempo.
Conclusão
Usar Redis com Node.js pode melhorar muito a resposta de APIs e separar tarefas pesadas do fluxo principal. O cache-aside reduz consultas repetidas, listas permitem compreender filas e contadores ajudam em limites e métricas.
Comece com um caso simples e mensurável. Defina expiração, implemente invalidação e acompanhe o uso de memória. Quando a fila crescer em importância, adote uma biblioteca especializada. Com uma estratégia clara, Redis deixa de ser apenas uma tecnologia rápida e passa a resolver problemas concretos de desempenho e coordenação.




