Skip to content

NOTE

O cliente Redis do Bun suporta versões 7.2 e superiores do servidor Redis.

O Bun fornece bindings nativos para trabalhar com bancos de dados Redis com uma API moderna baseada em Promises. A interface é projetada para ser simples e performática, com gerenciamento de conexão built-in, respostas totalmente tipadas e suporte a TLS.

ts
import { redis } from "bun";

// Define uma chave
await redis.set("greeting", "Hello from Bun!");

// Obtém uma chave
const greeting = await redis.get("greeting");
console.log(greeting); // "Hello from Bun!"

// Incrementa um contador
await redis.set("counter", 0);
await redis.incr("counter");

// Verifica se uma chave existe
const exists = await redis.exists("greeting");

// Deleta uma chave
await redis.del("greeting");

Começando

Para usar o cliente Redis, você primeiro precisa criar uma conexão:

ts
import { redis, RedisClient } from "bun";

// Usando o cliente padrão (lê informações de conexão do ambiente)
// process.env.REDIS_URL é usado por padrão
await redis.set("hello", "world");
const result = await redis.get("hello");

// Criando um cliente personalizado
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");

Por padrão, o cliente lê informações de conexão das seguintes variáveis de ambiente (em ordem de precedência):

  • REDIS_URL
  • VALKEY_URL
  • Se não definido, usa "redis://localhost:6379" como padrão

Ciclo de Vida da Conexão

O cliente Redis automaticamente gerencia conexões em segundo plano:

ts
// Nenhuma conexão é feita até que um comando seja executado
const client = new RedisClient();

// O primeiro comando inicia a conexão
await client.set("key", "value");

// A conexão permanece aberta para comandos subsequentes
await client.get("key");

// Fecha explicitamente a conexão quando terminar
client.close();

Você também pode controlar manualmente o ciclo de vida da conexão:

ts
const client = new RedisClient();

// Conecta explicitamente
await client.connect();

// Executa comandos
await client.set("key", "value");

// Desconecta quando terminar
client.close();

Operações Básicas

Operações com String

ts
// Define uma chave
await redis.set("user:1:name", "Alice");

// Obtém uma chave
const name = await redis.get("user:1:name");

// Obtém uma chave como Uint8Array
const buffer = await redis.getBuffer("user:1:name");

// Deleta uma chave
await redis.del("user:1:name");

// Verifica se uma chave existe
const exists = await redis.exists("user:1:name");

// Define expiração (em segundos)
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // expira em 1 hora

// Obtém tempo de vida restante (em segundos)
const ttl = await redis.ttl("session:123");

Operações Numéricas

ts
// Define valor inicial
await redis.set("counter", "0");

// Incrementa por 1
await redis.incr("counter");

// Decrementa por 1
await redis.decr("counter");

Operações com Hash

ts
// Define múltiplos campos em um hash
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);

// Obtém múltiplos campos de um hash
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]

// Obtém um único campo de um hash (retorna valor diretamente, null se ausente)
const userName = await redis.hget("user:123", "name");
console.log(userName); // "Alice"

// Incrementa um campo numérico em um hash
await redis.hincrby("user:123", "visits", 1);

// Incrementa um campo float em um hash
await redis.hincrbyfloat("user:123", "score", 1.5);

Operações com Set

ts
// Adiciona membro a um set
await redis.sadd("tags", "javascript");

// Remove membro de um set
await redis.srem("tags", "javascript");

// Verifica se membro existe em um set
const isMember = await redis.sismember("tags", "javascript");

// Obtém todos os membros de um set
const allTags = await redis.smembers("tags");

// Obtém um membro aleatório
const randomTag = await redis.srandmember("tags");

// Pop (remove e retorna) um membro aleatório
const poppedTag = await redis.spop("tags");

Pub/Sub

O Bun fornece bindings nativos para o protocolo Redis Pub/Sub. Novo no Bun 1.2.23

Uso Básico

Para começar a publicar mensagens, você pode configurar um publisher em publisher.ts:

typescript
import { RedisClient } from "bun";

const writer = new RedisClient("redis://localhost:6739");
await writer.connect();

writer.publish("general", "Hello everyone!");

writer.close();

Em outro arquivo, crie o subscriber em subscriber.ts:

typescript
import { RedisClient } from "bun";

const listener = new RedisClient("redis://localhost:6739");
await listener.connect();

await listener.subscribe("general", (message, channel) => {
  console.log(`Received: ${message}`);
});

Em um shell, execute seu subscriber:

bash
bun run subscriber.ts

e, em outro, execute seu publisher:

bash
bun run publisher.ts

NOTE

O modo de subscription assume o controle da conexão do `RedisClient`. Um cliente com subscriptions só pode chamar `RedisClient.prototype.subscribe()`. Em outras palavras, aplicações que precisam enviar mensagens para Redis precisam de uma conexão separada, obtível através de `.duplicate()`:
ts
import { RedisClient } from "bun";

const redis = new RedisClient("redis://localhost:6379");
await redis.connect();
const subscriber = await redis.duplicate(); 

await subscriber.subscribe("foo", () => {});
await redis.set("bar", "baz");

Publicando

Publicar mensagens é feito através do método publish():

typescript
await client.publish(channelName, message);

Subscriptions

O RedisClient do Bun permite que você se inscreva em canais através do método .subscribe():

typescript
await client.subscribe(channel, (message, channel) => {});

Você pode cancelar inscrição através do método .unsubscribe():

typescript
await client.unsubscribe(); // Cancela inscrição de todos os canais.
await client.unsubscribe(channel); // Cancela inscrição de um canal específico.
await client.unsubscribe(channel, listener); // Cancela inscrição de um listener específico.

Uso Avançado

Execução de Comandos e Pipelining

O cliente automaticamente faz pipeline de comandos, melhorando a performance ao enviar múltiplos comandos em um lote e processar respostas conforme chegam.

ts
// Comandos são automaticamente enfileirados em pipeline por padrão
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);

Para desabilitar o pipelining automático, você pode definir a opção enableAutoPipelining como false:

ts
const client = new RedisClient("redis://localhost:6379", {
  enableAutoPipelining: false, 
});

Comandos Raw

Quando você precisa usar comandos que não têm métodos de conveniência, você pode usar o método send:

ts
// Executa qualquer comando Redis
const info = await redis.send("INFO", []);

// LPUSH para uma lista
await redis.send("LPUSH", ["mylist", "value1", "value2"]);

// Obtém range de uma lista
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);

O método send permite que você use qualquer comando Redis, mesmo aqueles que não têm métodos dedicados no cliente. O primeiro argumento é o nome do comando, e o segundo argumento é um array de argumentos string.

Eventos de Conexão

Você pode registrar handlers para eventos de conexão:

ts
const client = new RedisClient();

// Chamado quando conectado com sucesso ao servidor Redis
client.onconnect = () => {
  console.log("Connected to Redis server");
};

// Chamado quando desconectado do servidor Redis
client.onclose = error => {
  console.error("Disconnected from Redis server:", error);
};

// Conecta/desconecta manualmente
await client.connect();
client.close();

Monitoramento e Status da Conexão

ts
// Verifica se está conectado
console.log(client.connected); // boolean indicando status da conexão

// Verifica quantidade de dados em buffer (em bytes)
console.log(client.bufferedAmount);

Conversão de Tipos

O cliente Redis lida com conversão automática de tipos para respostas Redis:

  • Respostas integer são retornadas como números JavaScript
  • Bulk strings são retornadas como strings JavaScript
  • Simple strings são retornadas como strings JavaScript
  • Null bulk strings são retornadas como null
  • Array responses são retornadas como arrays JavaScript
  • Error responses lançam erros JavaScript com códigos de erro apropriados
  • Boolean responses (RESP3) são retornadas como booleanos JavaScript
  • Map responses (RESP3) são retornadas como objetos JavaScript
  • Set responses (RESP3) são retornadas como arrays JavaScript

Tratamento especial para comandos específicos:

  • EXISTS retorna um boolean ao invés de número (1 vira true, 0 vira false)
  • SISMEMBER retorna um boolean (1 vira true, 0 vira false)

Os seguintes comandos desabilitam o pipelining automático:

  • AUTH
  • INFO
  • QUIT
  • EXEC
  • MULTI
  • WATCH
  • SCRIPT
  • SELECT
  • CLUSTER
  • DISCARD
  • UNWATCH
  • PIPELINE
  • SUBSCRIBE
  • UNSUBSCRIBE
  • UNPSUBSCRIBE

Opções de Conexão

Ao criar um cliente, você pode passar várias opções para configurar a conexão:

ts
const client = new RedisClient("redis://localhost:6379", {
  // Timeout de conexão em milissegundos (padrão: 10000)
  connectionTimeout: 5000,

  // Timeout de idle em milissegundos (padrão: 0 = sem timeout)
  idleTimeout: 30000,

  // Se deve reconectar automaticamente após desconexão (padrão: true)
  autoReconnect: true,

  // Número máximo de tentativas de reconexão (padrão: 10)
  maxRetries: 10,

  // Se deve enfileirar comandos quando desconectado (padrão: true)
  enableOfflineQueue: true,

  // Se deve automaticamente fazer pipeline de comandos (padrão: true)
  enableAutoPipelining: true,

  // Opções TLS (padrão: false)
  tls: true,
  // Alternativamente, forneça configuração TLS personalizada:
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   cert: "path/to/cert.pem",
  //   key: "path/to/key.pem",
  // }
});

Comportamento de Reconexão

Quando uma conexão é perdida, o cliente automaticamente tenta reconectar com backoff exponencial:

  1. O cliente começa com um pequeno delay (50ms) e dobra a cada tentativa
  2. O delay de reconexão é limitado a 2000ms (2 segundos)
  3. O cliente tenta reconectar até maxRetries vezes (padrão: 10)
  4. Comandos executados durante desconexão são:
    • Enfileirados se enableOfflineQueue for true (padrão)
    • Rejeitados imediatamente se enableOfflineQueue for false

Formatos de URL Suportados

O cliente Redis suporta vários formatos de URL:

ts
// URL Redis padrão
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");

// Com autenticação
new RedisClient("redis://username:password@localhost:6379");

// Com número de database
new RedisClient("redis://localhost:6379/0");

// Conexões TLS
new RedisClient("rediss://localhost:6379");
new RedisClient("rediss://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
new RedisClient("redis+tls://localhost:6379");

// Conexões Unix socket
new RedisClient("redis+unix:///path/to/socket");
new RedisClient("redis+unix:///path/to/socket");

// TLS sobre Unix socket
new RedisClient("redis+tls+unix:///path/to/socket");
new RedisClient("redis+tls+unix:///path/to/socket");

Tratamento de Erros

O cliente Redis lança erros tipados para diferentes cenários:

ts
try {
  await redis.get("non-existent-key");
} catch (error) {
  if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
    console.error("Connection to Redis server was closed");
  } else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
    console.error("Authentication failed");
  } else {
    console.error("Unexpected error:", error);
  }
}

Códigos de erro comuns:

  • ERR_REDIS_CONNECTION_CLOSED - Conexão com o servidor foi fechada
  • ERR_REDIS_AUTHENTICATION_FAILED - Falha ao autenticar com o servidor
  • ERR_REDIS_INVALID_RESPONSE - Recebida uma resposta inválida do servidor

Exemplos de Casos de Uso

Cache

ts
async function getUserWithCache(userId) {
  const cacheKey = `user:${userId}`;

  // Tenta obter do cache primeiro
  const cachedUser = await redis.get(cacheKey);
  if (cachedUser) {
    return JSON.parse(cachedUser);
  }

  // Não está no cache, busca do banco de dados
  const user = await database.getUser(userId);

  // Armazena no cache por 1 hora
  await redis.set(cacheKey, JSON.stringify(user));
  await redis.expire(cacheKey, 3600);

  return user;
}

Rate Limiting

ts
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
  const key = `ratelimit:${ip}`;

  // Incrementa contador
  const count = await redis.incr(key);

  // Define expiração se esta for a primeira requisição na janela
  if (count === 1) {
    await redis.expire(key, windowSecs);
  }

  // Verifica se limite excedido
  return {
    limited: count > limit,
    remaining: Math.max(0, limit - count),
  };
}

Armazenamento de Sessão

ts
async function createSession(userId, data) {
  const sessionId = crypto.randomUUID();
  const key = `session:${sessionId}`;

  // Armazena sessão com expiração
  await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
  await redis.expire(key, 86400); // 24 horas

  return sessionId;
}

async function getSession(sessionId) {
  const key = `session:${sessionId}`;

  // Obtém dados da sessão
  const exists = await redis.exists(key);
  if (!exists) return null;

  const [userId, created, data] = await redis.hmget(key, ["userId", "created", "data"]);

  return {
    userId: Number(userId),
    created: Number(created),
    data: JSON.parse(data),
  };
}

Notas de Implementação

O cliente Redis do Bun é implementado em Zig e usa o Redis Serialization Protocol (RESP3). Ele gerencia conexões de forma eficiente e fornece reconexão automática com backoff exponencial.

O cliente suporta pipelining de comandos, significando que múltiplos comandos podem ser enviados sem esperar pelas respostas de comandos anteriores. Isto melhora significativamente a performance ao enviar múltiplos comandos em sucessão.

Limitações e Planos Futuros

Limitações atuais do cliente Redis que planejamos abordar em versões futuras:

  • Transactions (MULTI/EXEC) devem ser feitas através de comandos raw por enquanto

Funcionalidades não suportadas:

  • Redis Sentinel
  • Redis Cluster

Bun by www.bunjs.com.cn edit