Usando o Cliente Redis Nativo do Bun
Guia do cliente Redis integrado do Bun: conexão, comandos tipados, Pub/Sub, auto-pipelining, rate limiting e quando usar ioredis.
O Bun inclui um cliente Redis nativo que pode ser importado diretamente — import { redis, RedisClient } from "bun" — portanto, para a maioria das cargas de trabalho de cache, sessão, pub/sub e rate limiting, você não precisa mais adicionar ioredis ou node-redis. O cliente Redis nativo do Bun possui uma API baseada em Promises com gerenciamento de conexão integrado, respostas totalmente tipadas e suporte a TLS, além de ser compatível com versões do servidor Redis 7.2 ou superior. Este guia trata especificamente desse cliente nativo — não do ioredis executando sobre o Bun, que é o que a maioria dos tutoriais de “Redis com Bun” demonstra silenciosamente.
Essa distinção é importante. Um padrão comum nos tutoriais existentes sobre Bun com Redis é executar bun add ioredis e chamar new Redis() — isso é a biblioteca de terceiros, não a API do runtime. Tudo a seguir utiliza a API que já vem integrada ao próprio Bun.
Principais Conclusões
- Importe o cliente com
import { redis, RedisClient } from "bun"; a instância padrãoredislêREDIS_URL, depoisVALKEY_URL, e usaredis://localhost:6379como fallback. - O cliente não abre nenhuma conexão até que o primeiro comando seja executado, realiza pipelining automático de comandos e reconecta com backoff exponencial a partir de 50ms até um limite máximo de 2000ms, com 10 tentativas por padrão.
- A partir do Bun 1.3, o cliente encapsula 66 comandos Redis como métodos tipados;
redis.send(command, argsArray)executa qualquer comando que não esteja encapsulado — que é também a forma de emitirMULTI/EXECatualmente. - O Pub/Sub assume o controle de uma conexão, por isso você chama
.duplicate()para obter uma segunda conexão destinada à publicação ou à execução de outros comandos. - A partir do Bun 1.3.x, o cliente nativo não suporta Redis Cluster ou Sentinel; para esses casos, utilize
node-redisouioredis.
Conectando com o cliente Redis nativo
O caminho mais rápido é a exportação padrão redis, que se conecta de forma lazy a partir de variáveis de ambiente. Por padrão, o cliente lê as informações de conexão de REDIS_URL, depois de VALKEY_URL e, se nenhuma estiver definida, usa redis://localhost:6379 como padrão. Nenhum socket é aberto até que você emita o primeiro comando.
import { redis, RedisClient } from "bun";
// Cliente padrão — lê REDIS_URL / VALKEY_URL do ambiente
await redis.set("hello", "world");
const result = await redis.get("hello");
// Cliente personalizado com URL explícita
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");
Use a instância padrão redis para acesso em toda a aplicação; construa seu próprio RedisClient quando precisar de uma conexão separada, opções distintas ou um banco de dados diferente. A documentação do Bun Redis lista o conjunto completo de esquemas de URL suportados, incluindo rediss:// e redis+tls:// para TLS e redis+unix:// para Unix sockets.
O gerenciamento de conexão é tratado automaticamente:
- Nenhuma conexão é estabelecida até que um comando seja executado; o primeiro comando inicia a conexão, que permanece aberta para os comandos subsequentes até que você chame
client.close(). - Quando uma conexão cai, o cliente se recupera por conta própria: começa com um atraso de 50ms, dobrando a cada tentativa, com o atraso de reconexão limitado a 2000ms e até
maxRetriestentativas (padrão 10). - Comandos emitidos enquanto desconectado são enfileirados quando
enableOfflineQueueétrue(o padrão) e rejeitados imediatamente quando éfalse.
Esses são os mesmos comportamentos que você precisaria configurar manualmente no ioredis.
Discover how at OpenReplay.com.
Operações principais e o fallback send()
O cliente expõe métodos tipados para os comandos Redis do dia a dia e converte as respostas em valores JavaScript nativos, dispensando a maior parte da análise manual:
- respostas inteiras são retornadas como números JavaScript;
- strings bulk e simples como strings;
- strings bulk nulas como
null; e - arrays como arrays JavaScript.
Há também coerção específica por comando: EXISTS retorna um booleano em vez de um número, e SISMEMBER retorna um booleano.
Ajuste qualquer um desses comportamentos através do objeto de opções:
const client = new RedisClient("redis://localhost:6379", {
connectionTimeout: 5000, // padrão 10000 ms
autoReconnect: true, // padrão true
maxRetries: 10, // padrão 10
enableOfflineQueue: true, // padrão true
enableAutoPipelining: true, // padrão true
tls: true,
});
// Strings e expiração
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // segundos
const ttl = await redis.ttl("session:123");
// Contadores
await redis.set("counter", "0");
await redis.incr("counter");
await redis.decr("counter");
// exists() retorna um booleano
const present = await redis.exists("session:123"); // true
// Hashes
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com"]);
const [name, email] = await redis.hmget("user:123", ["name", "email"]);
Nem todo comando possui um método dedicado. Todas as operações padrão são suportadas — incluindo hashes, listas e sets — totalizando 66 comandos a partir do Bun 1.3. Para qualquer coisa fora desse conjunto, use send(). O método send executa qualquer comando Redis, incluindo aqueles sem um método dedicado; o primeiro argumento é o nome do comando e o segundo é um array de argumentos em string.
// Fallback de comando raw — nome + array de argumentos em string
await redis.send("LPUSH", ["mylist", "value1", "value2"]);
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);
send() também é a forma de executar transações atualmente, que é a principal lacuna da API do cliente abordada mais adiante.
Pub/Sub com .duplicate()
O Pub/Sub do Redis é suportado, mas uma conexão com assinatura ativa não pode fazer mais nada. A assinatura assume o controle da conexão do RedisClient: um cliente com assinaturas só pode chamar subscribe(), portanto, para enviar outros comandos, você cria uma conexão separada com .duplicate(). O callback de assinatura recebe primeiro a mensagem e depois o canal.
import { RedisClient } from "bun";
const redis = new RedisClient("redis://localhost:6379");
await redis.connect();
// Segunda conexão para publicação / outros comandos
const subscriber = await redis.duplicate();
await subscriber.subscribe("notifications", (message, channel) => {
console.log(`[${channel}] ${message}`);
});
await redis.publish("notifications", "Hello from Bun!");
O Pub/Sub foi adicionado no Bun 1.2.23, e a documentação ainda o marca como experimental — estável na prática, mas vale fixar a versão do Bun se você depender dele. Cancele assinaturas com .unsubscribe() (sem argumento limpa todos os canais; passe um canal ou listener para restringir o escopo).
Pipelining automático
O cliente nativo realiza pipelining por padrão — sem necessidade de configuração. O cliente automaticamente agrupa comandos em pipelines, melhorando o desempenho ao enviar múltiplos comandos em lote e processar as respostas conforme chegam. Emita vários comandos com await juntos (por exemplo, com Promise.all) e eles serão enviados em um único lote, em vez de uma ida e volta por comando:
const [a, b, c] = await Promise.all([
redis.get("user:1:name"),
redis.get("user:2:name"),
redis.get("user:3:name"),
]);
O pipelining automático é onde a diferença de desempenho em relação ao ioredis se torna mais evidente. O Bun descreve seu cliente Redis como significativamente mais rápido que o ioredis, com a vantagem aumentando conforme o tamanho do lote cresce. Os números publicados e reproduzíveis do Bun nas notas de lançamento da versão 1.2.9 confirmam isso: no benchmark de GET, o Bun.redis foi 44,82% mais rápido em lotes de 10, 58,29% mais rápido em lotes de 100 e 85,39% mais rápido em lotes de 1000 — aproximadamente 1,85x no maior lote, não um múltiplo fixo. (O próprio blog de lançamento do Bun 1.3 destaca separadamente um gráfico de benchmark colocando o cliente nativo com mais de 7,9x o throughput do ioredis — portanto, esse número é do próprio Bun, não apenas de fontes secundárias.)
Alguns comandos desativam o auto-pipelining por serem stateful — entre eles AUTH, INFO, MULTI, EXEC, WATCH, SUBSCRIBE e SELECT. Para desativar o pipelining globalmente, defina enableAutoPipelining: false.
Padrões aplicados: cache, sessões e rate limiting
Os padrões abaixo funcionam com qualquer cliente Redis; aqui eles rodam inteiramente na API nativa. Trate-os como um pequeno módulo de serviço.
Cache-aside. Verifique o Redis primeiro, recorra ao banco de dados em caso de miss e, em seguida, popule o cache com um TTL.
import { redis } from "bun";
async function getUser(userId: string) {
const cacheKey = `user:${userId}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await db.getUser(userId); // sua fonte de dados
await redis.set(cacheKey, JSON.stringify(user));
await redis.expire(cacheKey, 3600); // 1 hora
return user;
}
Para write-through, atualize o cache na mesma operação que escreve no banco de dados. Para stale-while-revalidate, sirva o valor em cache imediatamente e atualize-o em segundo plano quando o TTL estiver próximo do fim.
Armazenamento de sessão com TTL. Armazene sessões como hashes e deixe o Redis expirá-las.
async function createSession(userId: number, data: object) {
const sessionId = crypto.randomUUID();
const key = `session:${sessionId}`;
await redis.hmset(key, [
"userId", String(userId),
"created", String(Date.now()),
"data", JSON.stringify(data),
]);
await redis.expire(key, 86400); // 24 horas
return sessionId;
}
Rate limiting com janela deslizante usando sorted sets. Os comandos de sorted set não estão encapsulados como métodos dedicados, então este é um lugar natural para send(). Remova as entradas mais antigas que a janela, conte o que resta e adicione a requisição atual.
async function rateLimit(id: string, limit = 100, windowMs = 60_000) {
const key = `ratelimit:${id}`;
const now = Date.now();
await redis.send("ZREMRANGEBYSCORE", [key, "0", String(now - windowMs)]);
const count = Number(await redis.send("ZCARD", [key]));
if (count >= limit) return { allowed: false, remaining: 0 };
await redis.send("ZADD", [key, String(now), `${now}-${crypto.randomUUID()}`]);
await redis.expire(key, Math.ceil(windowMs / 1000));
return { allowed: true, remaining: limit - count - 1 };
}
Como o cliente realiza auto-pipelining, a sequência ZREMRANGEBYSCORE/ZCARD/ZADD/EXPIRE é agrupada de forma eficiente sem a necessidade de objetos de pipeline manuais.
Limitações e quando ainda usar ioredis ou node-redis
O cliente nativo cobre os casos comuns acima, mas possui lacunas documentadas a partir do Bun 1.3.x. Transações (MULTI/EXEC) devem ser feitas através de comandos raw, e Redis Sentinel e Redis Cluster não são suportados. Se você precisar de clustering, failover baseado em Sentinel ou módulos do Redis Stack (search, JSON, time-series, estruturas probabilísticas), recorra a uma biblioteca completa. A própria issue do Bun que acompanha a migração recomenda o node-redis como o cliente preferido para novos projetos, com o ioredis como a alternativa consolidada. O blog da versão 1.3 menciona que o suporte a clusters, streams e scripts Lua está em desenvolvimento — portanto, verifique a seção de Limitações da documentação antes de assumir o status atual, pois essa área ainda está em evolução.
| Funcionalidade | redis nativo do Bun | node-redis / ioredis |
|---|---|---|
| Etapa de instalação | Nenhuma — já vem com o Bun | bun add node-redis / ioredis |
| Auto-pipelining | Ativado por padrão | Configurável / manual |
| Pub/Sub | Sim (experimental, via .duplicate()) | Sim |
| Transações (MULTI/EXEC) | Apenas via send() raw | API nativa |
| Redis Cluster | Não suportado | Suportado |
| Redis Sentinel | Não suportado | Suportado |
| Módulos Redis Stack | Use uma biblioteca | Suportado pelo node-redis |
Uma observação de implementação que merece ressalva: a documentação descreve o cliente como compilado nativamente (atualmente caracterizado como implementado em Rust, após a migração do Bun em 2026 para longe do Zig). Esse detalhe não altera uma única linha do código da sua aplicação — a superfície da API é idêntica independentemente da linguagem subjacente.
Para cache, sessões, pub/sub, contadores e rate limiting, o cliente nativo é suficiente, e eliminar uma dependência é um ganho real. No momento em que você precisar de Cluster, Sentinel, Redis Stack ou fluxos transacionais mais ricos, instale o node-redis. Comece com import { redis } from "bun" contra um servidor local, confirme que o conjunto de comandos necessários está coberto e fixe a versão do Bun para que uma API em evolução não te surpreenda em produção.
Perguntas Frequentes
Como executo uma transação Redis com o cliente nativo do Bun?
O cliente nativo do Bun não possui uma API nativa para MULTI/EXEC, portanto, você executa transações através do método raw send(). Emita redis.send('MULTI', []), depois seus comandos enfileirados e, por fim, redis.send('EXEC', []). Como MULTI, EXEC e WATCH desativam o auto-pipelining, eles são enviados individualmente em vez de agrupados. Se você precisar de uma API de transação nativa mais rica, node-redis ou ioredis oferecem essa funcionalidade.
O cliente Redis nativo do Bun funciona com Redis Cluster ou Sentinel?
Não. A partir do Bun 1.3.x, o cliente nativo não suporta Redis Cluster ou Redis Sentinel, conforme a seção de Limitações da documentação. Para clustering com sharding, failover baseado em Sentinel ou módulos do Redis Stack como search, JSON e time-series, use uma biblioteca completa. A própria issue de migração do Bun recomenda o node-redis como o cliente preferido para novos projetos, com o ioredis como a alternativa consolidada.
Qual é a diferença entre usar o import redis nativo do Bun e executar o ioredis no Bun?
O cliente nativo é a API do runtime acessada através de import { redis, RedisClient } from 'bun', sem necessidade de instalação e com pipelining de comandos por padrão. Usar o ioredis significa executar bun add ioredis e new Redis(), uma biblioteca de terceiros com seu próprio gerenciamento de conexão. A maioria dos tutoriais de 'Redis com Bun' usa silenciosamente o ioredis. O cliente nativo não possui dependências; o ioredis adiciona APIs nativas de Cluster, Sentinel e transações que o cliente nativo não possui.
O que acontece com os comandos enviados enquanto o cliente Redis do Bun está desconectado?
Comandos emitidos enquanto o cliente está desconectado são enfileirados e reenviados assim que a conexão for restaurada, pois enableOfflineQueue é true por padrão. O cliente reconecta automaticamente usando backoff exponencial a partir de 50ms, dobrando a cada tentativa, com limite máximo de 2000ms, ao longo de 10 tentativas por padrão. Defina enableOfflineQueue como false para rejeitar comandos imediatamente durante uma desconexão, em vez de armazená-los em buffer.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k