12k
All articles

Uso del cliente Redis integrado de Bun

Guía del cliente Redis integrado de Bun: conexión, comandos tipados, Pub/Sub, auto-pipelining, rate limiting y cuándo usar ioredis.

OpenReplay Team
OpenReplay Team
Uso del cliente Redis integrado de Bun

Bun incluye un cliente Redis integrado que se importa directamente — import { redis, RedisClient } from "bun" — por lo que para la mayoría de las cargas de trabajo de caché, sesiones, pub/sub y limitación de tasa ya no es necesario agregar ioredis ni node-redis. El cliente Redis nativo de Bun ofrece una API basada en Promises con gestión de conexiones integrada, respuestas completamente tipadas y soporte para TLS, y es compatible con versiones de servidor Redis 7.2 en adelante. Esta guía trata específicamente sobre ese cliente integrado — no sobre ioredis ejecutándose en Bun, que es lo que la mayoría de los tutoriales de “Redis con Bun” demuestran sin mencionarlo explícitamente.

La distinción es importante. Un patrón habitual en los tutoriales existentes de Bun con Redis es ejecutar bun add ioredis y llamar a new Redis() — eso corresponde a la biblioteca de terceros, no a la API del runtime. Todo lo que se muestra a continuación utiliza la API que viene incluida en el propio Bun.

Puntos clave

  • Importa el cliente con import { redis, RedisClient } from "bun"; la instancia predeterminada redis lee REDIS_URL, luego VALKEY_URL, y si ninguna está definida, utiliza redis://localhost:6379 como valor por defecto.
  • El cliente no abre ninguna conexión hasta que se ejecuta el primer comando, canaliza comandos automáticamente y se reconecta con retroceso exponencial desde 50ms hasta un límite de 2000ms en un máximo predeterminado de 10 reintentos.
  • A partir de Bun 1.3, el cliente expone 66 comandos Redis como métodos tipados; redis.send(command, argsArray) ejecuta cualquier comando que no esté disponible como método — que es también la forma de emitir MULTI/EXEC actualmente.
  • Pub/Sub ocupa una conexión de forma exclusiva, por lo que se debe llamar a .duplicate() para obtener una segunda conexión destinada a publicar o ejecutar otros comandos.
  • A partir de Bun 1.3.x, el cliente integrado no soporta Redis Cluster ni Sentinel; para esos casos, utiliza node-redis o ioredis.

Conexión con el cliente Redis integrado

La forma más rápida es usar la exportación predeterminada redis, que se conecta de forma diferida a partir de variables de entorno. Por defecto, el cliente lee la información de conexión desde REDIS_URL, luego VALKEY_URL, y si ninguna está definida, utiliza redis://localhost:6379. No se abre ningún socket hasta que se emite el primer comando.

import { redis, RedisClient } from "bun";

// Cliente predeterminado — lee REDIS_URL / VALKEY_URL del entorno
await redis.set("hello", "world");
const result = await redis.get("hello");

// Cliente personalizado con una URL explícita
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");

Utiliza la instancia predeterminada redis para el acceso a nivel de aplicación; construye tu propio RedisClient cuando necesites una conexión separada, opciones distintas o una base de datos diferente. La documentación de Redis en Bun lista el conjunto completo de esquemas de URL soportados, incluyendo rediss:// y redis+tls:// para TLS, y redis+unix:// para sockets Unix.

La gestión de conexiones se maneja automáticamente:

  • No se establece ninguna conexión hasta que se ejecuta un comando; el primer comando inicia la conexión, que permanece abierta para los comandos posteriores hasta que se llama a client.close().
  • Cuando una conexión se interrumpe, el cliente se recupera por sí solo: comienza con un retraso de 50ms y lo duplica en cada intento, con un límite máximo de 2000ms, reintentando hasta maxRetries veces (10 por defecto).
  • Los comandos emitidos durante una desconexión se encolan cuando enableOfflineQueue es true (valor predeterminado) y se rechazan inmediatamente cuando es false.

Estos son los mismos comportamientos que de otro modo habría que configurar manualmente en ioredis.

Operaciones principales y el método de reserva send()

El cliente expone métodos tipados para los comandos Redis más comunes y convierte las respuestas a valores nativos de JavaScript, lo que evita la mayor parte del análisis manual:

  • Las respuestas de tipo entero se devuelven como números de JavaScript;
  • Las cadenas de texto simples y masivas como strings;
  • Las cadenas masivas nulas como null; y
  • Los arreglos como arrays de JavaScript.

También existe coerción específica por comando: EXISTS devuelve un booleano en lugar de un número, y SISMEMBER también devuelve un booleano.

Cualquiera de estos comportamientos puede ajustarse mediante el objeto de opciones:

const client = new RedisClient("redis://localhost:6379", {
  connectionTimeout: 5000,     // predeterminado: 10000 ms
  autoReconnect: true,         // predeterminado: true
  maxRetries: 10,              // predeterminado: 10
  enableOfflineQueue: true,    // predeterminado: true
  enableAutoPipelining: true,  // predeterminado: true
  tls: true,
});
// Strings y expiración
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() devuelve un 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"]);

No todos los comandos tienen un método dedicado. Todas las operaciones estándar están soportadas — incluyendo hashes, listas y conjuntos — con un total de 66 comandos a partir de Bun 1.3. Para cualquier operación fuera de ese conjunto, utiliza send(). El método send ejecuta cualquier comando Redis, incluyendo aquellos sin un método dedicado; el primer argumento es el nombre del comando y el segundo es un array de argumentos en forma de strings.

// Comando sin método dedicado — nombre + array de argumentos string
await redis.send("LPUSH", ["mylist", "value1", "value2"]);
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);

send() es también la forma de ejecutar transacciones actualmente, que es la principal limitación de la API del cliente que se aborda más adelante.

Pub/Sub con .duplicate()

Redis Pub/Sub está soportado, pero una conexión suscrita no puede hacer nada más. La suscripción ocupa la conexión del RedisClient: un cliente con suscripciones activas solo puede llamar a subscribe(), por lo que para enviar otros comandos es necesario crear una conexión separada con .duplicate(). El callback de suscripción recibe primero el mensaje y luego el canal.

import { RedisClient } from "bun";

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

// Segunda conexión para publicar / ejecutar otros comandos
const subscriber = await redis.duplicate();

await subscriber.subscribe("notifications", (message, channel) => {
  console.log(`[${channel}] ${message}`);
});

await redis.publish("notifications", "Hello from Bun!");

Pub/Sub se añadió en Bun 1.2.23, y la documentación aún lo marca como experimental — estable en la práctica, pero vale la pena fijar la versión de Bun si dependes de esta funcionalidad. Para cancelar suscripciones, utiliza .unsubscribe() (sin argumento elimina todos los canales; pasa un canal o un listener para limitar el alcance).

Canalización automática

El cliente integrado canaliza comandos por defecto — no es necesario activarlo explícitamente. El cliente canaliza automáticamente los comandos, mejorando el rendimiento al enviarlos en lotes y procesando las respuestas a medida que llegan. Al emitir varios comandos con await de forma simultánea (por ejemplo, con Promise.all), estos se envían en un único lote en lugar de un viaje de ida y vuelta por cada uno:

const [a, b, c] = await Promise.all([
  redis.get("user:1:name"),
  redis.get("user:2:name"),
  redis.get("user:3:name"),
]);

La canalización automática es donde la brecha de rendimiento con ioredis se amplía. Bun describe su cliente Redis como significativamente más rápido que ioredis, con una ventaja que aumenta conforme crece el tamaño del lote. Las cifras publicadas y reproducibles de Bun en las notas de la versión 1.2.9 lo respaldan: en su benchmark de GET, Bun.redis fue un 44,82% más rápido con lotes de 10, un 58,29% más rápido con lotes de 100 y un 85,39% más rápido con lotes de 1000 — aproximadamente 1,85x en el lote más grande, no un múltiplo fijo. (El propio blog de la versión 1.3 de Bun publica por separado un gráfico de benchmark que sitúa al cliente integrado en más de 7,9x el rendimiento de ioredis, por lo que esa cifra es un número publicado por el propio Bun, no solo un reporte secundario.)

Un pequeño conjunto de comandos desactiva la canalización automática por ser de naturaleza con estado — entre ellos AUTH, INFO, MULTI, EXEC, WATCH, SUBSCRIBE y SELECT. Para desactivar la canalización globalmente, establece enableAutoPipelining: false.

Patrones aplicados: caché, sesiones y limitación de tasa

Los patrones que se muestran a continuación son transferibles desde cualquier cliente Redis; aquí se ejecutan íntegramente sobre la API integrada. Trátalo como un pequeño módulo de servicio.

Caché lateral (cache-aside). Consulta Redis primero, recurre a la base de datos en caso de fallo de caché, y luego almacena el resultado con un 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);       // tu fuente de datos
  await redis.set(cacheKey, JSON.stringify(user));
  await redis.expire(cacheKey, 3600);          // 1 hora
  return user;
}

Para escritura directa (write-through), actualiza la caché en la misma operación que escribe en la base de datos. Para stale-while-revalidate, sirve el valor en caché de inmediato y actualízalo en segundo plano cuando el TTL esté próximo a expirar.

Almacenamiento de sesiones con TTL. Almacena las sesiones como hashes y deja que Redis las expire automáticamente.

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;
}

Limitación de tasa con ventana deslizante mediante conjuntos ordenados. Los comandos de conjuntos ordenados no están disponibles como métodos dedicados, por lo que este es un caso natural para send(). Elimina las entradas anteriores a la ventana temporal, cuenta las restantes y agrega la solicitud actual.

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 };
}

Dado que el cliente canaliza automáticamente, la secuencia ZREMRANGEBYSCORE/ZCARD/ZADD/EXPIRE se agrupa de forma eficiente sin necesidad de objetos de pipeline manuales.

Limitaciones y cuándo seguir usando ioredis o node-redis

El cliente integrado cubre los casos comunes descritos anteriormente, pero tiene limitaciones documentadas a partir de Bun 1.3.x. Las transacciones (MULTI/EXEC) deben realizarse mediante comandos sin procesar, y Redis Sentinel y Redis Cluster no están soportados. Si necesitas clustering, failover basado en Sentinel o módulos de Redis Stack (búsqueda, JSON, series temporales, estructuras probabilísticas), recurre a una biblioteca con más funcionalidades. El issue de migración del propio Bun recomienda node-redis como cliente preferido para proyectos nuevos, con ioredis como alternativa consolidada. El blog de la versión 1.3 indica que el soporte para clusters, streams y scripting con Lua está en desarrollo — por lo que conviene revisar la sección de Limitaciones de la documentación antes de asumir el estado actual, ya que esta área sigue evolucionando.

Capacidadredis integrado de Bunnode-redis / ioredis
Paso de instalaciónNinguno — incluido en Bunbun add node-redis / ioredis
Canalización automáticaActivada por defectoConfigurable / manual
Pub/SubSí (experimental, mediante .duplicate())
Transacciones (MULTI/EXEC)Solo mediante send() sin procesarAPI nativa
Redis ClusterNo soportadoSoportado
Redis SentinelNo soportadoSoportado
Módulos de Redis StackUsar una bibliotecaSoportados en node-redis

Vale la pena mencionar un detalle de implementación con cierta reserva: la documentación describe el cliente como compilado de forma nativa (actualmente caracterizado como implementado en Rust, tras la migración de Bun en 2026 desde Zig). Ese detalle no cambia ni una sola línea del código de tu aplicación — la superficie de la API es idéntica independientemente del lenguaje subyacente.

Para caché, sesiones, pub/sub, contadores y limitación de tasa, el cliente integrado es suficiente, y eliminar una dependencia es una ventaja real. En el momento en que necesites Cluster, Sentinel, Redis Stack o flujos transaccionales complejos, instala node-redis. Comienza con import { redis } from "bun" contra un servidor local, confirma que el conjunto de comandos que necesitas está cubierto, y fija tu versión de Bun para que una API en evolución no te sorprenda en producción.

Preguntas frecuentes

¿Cómo ejecuto una transacción Redis con el cliente integrado de Bun?

El cliente integrado de Bun no tiene una API nativa para MULTI/EXEC, por lo que las transacciones se ejecutan mediante el método send(). Emite redis.send('MULTI', []), luego los comandos en cola, y finalmente redis.send('EXEC', []). Dado que MULTI, EXEC y WATCH desactivan la canalización automática, se envían de forma individual en lugar de en lote. Si necesitas una API de transacciones nativa más completa, node-redis o ioredis la proporcionan.

¿El cliente Redis integrado de Bun funciona con Redis Cluster o Sentinel?

No. A partir de Bun 1.3.x, el cliente integrado no soporta Redis Cluster ni Redis Sentinel, según la sección de Limitaciones de la documentación. Para clustering fragmentado, failover basado en Sentinel o módulos de Redis Stack como búsqueda, JSON y series temporales, utiliza una biblioteca con más funcionalidades. El propio issue de migración de Bun recomienda node-redis como cliente preferido para proyectos nuevos, con ioredis como alternativa consolidada.

¿Cuál es la diferencia entre usar la importación redis integrada de Bun y ejecutar ioredis en Bun?

El cliente integrado es la API del runtime a la que se accede mediante import { redis, RedisClient } from 'bun', sin necesidad de instalación y con canalización de comandos activada por defecto. Usar ioredis implica ejecutar bun add ioredis y new Redis(), una biblioteca de terceros con su propia gestión de conexiones. La mayoría de los tutoriales de 'Redis con Bun' utilizan ioredis sin mencionarlo explícitamente. El cliente integrado no tiene dependencias externas; ioredis añade APIs nativas para Cluster, Sentinel y transacciones que el cliente integrado no tiene.

¿Qué ocurre con los comandos enviados mientras el cliente Redis de Bun está desconectado?

Los comandos emitidos mientras el cliente está desconectado se encolan y se reproducen una vez que se restaura la conexión, ya que enableOfflineQueue tiene el valor true por defecto. El cliente se reconecta automáticamente usando retroceso exponencial comenzando en 50ms, duplicándose en cada intento, con un límite de 2000ms, durante un máximo predeterminado de 10 reintentos. Establece enableOfflineQueue en false para rechazar los comandos inmediatamente durante una desconexión en lugar de almacenarlos en búfer.

DevTools for the frontend

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

We use cookies to improve your experience. By using our site, you accept cookies.