NOTE
El cliente Redis de Bun soporta las versiones 7.2 y superiores del servidor Redis.Bun proporciona bindings nativos para trabajar con bases de datos Redis con una API moderna basada en Promesas. La interfaz está diseñada para ser simple y de alto rendimiento, con gestión de conexiones incorporada, respuestas completamente tipadas y soporte TLS.
import { redis } from "bun";
// Establecer una clave
await redis.set("greeting", "Hello from Bun!");
// Obtener una clave
const greeting = await redis.get("greeting");
console.log(greeting); // "Hello from Bun!"
// Incrementar un contador
await redis.set("counter", 0);
await redis.incr("counter");
// Verificar si existe una clave
const exists = await redis.exists("greeting");
// Eliminar una clave
await redis.del("greeting");Primeros Pasos
Para usar el cliente Redis, primero necesitas crear una conexión:
import { redis, RedisClient } from "bun";
// Usando el cliente predeterminado (lee la información de conexión del entorno)
// process.env.REDIS_URL se usa por defecto
await redis.set("hello", "world");
const result = await redis.get("hello");
// Crear un cliente personalizado
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");Por defecto, el cliente lee la información de conexión de las siguientes variables de entorno (en orden de precedencia):
REDIS_URLVALKEY_URL- Si no están establecidas, el valor predeterminado es
"redis://localhost:6379"
Ciclo de Vida de la Conexión
El cliente Redis maneja automáticamente las conexiones en segundo plano:
// No se realiza ninguna conexión hasta que se ejecuta un comando
const client = new RedisClient();
// El primer comando inicia la conexión
await client.set("key", "value");
// La conexión permanece abierta para comandos posteriores
await client.get("key");
// Cerrar explícitamente la conexión cuando se termine
client.close();También puedes controlar manualmente el ciclo de vida de la conexión:
const client = new RedisClient();
// Conectar explícitamente
await client.connect();
// Ejecutar comandos
await client.set("key", "value");
// Desconectar cuando se termine
client.close();Operaciones Básicas
Operaciones con Cadenas
// Establecer una clave
await redis.set("user:1:name", "Alice");
// Obtener una clave
const name = await redis.get("user:1:name");
// Obtener una clave como Uint8Array
const buffer = await redis.getBuffer("user:1:name");
// Eliminar una clave
await redis.del("user:1:name");
// Verificar si existe una clave
const exists = await redis.exists("user:1:name");
// Establecer expiración (en segundos)
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // expira en 1 hora
// Obtener tiempo de vida restante (en segundos)
const ttl = await redis.ttl("session:123");Operaciones Numéricas
// Establecer valor inicial
await redis.set("counter", "0");
// Incrementar en 1
await redis.incr("counter");
// Decrementar en 1
await redis.decr("counter");Operaciones con Hash
// Establecer múltiples campos en un hash
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);
// Obtener múltiples campos de un hash
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]
// Obtener un solo campo de un hash (devuelve el valor directamente, null si falta)
const userName = await redis.hget("user:123", "name");
console.log(userName); // "Alice"
// Incrementar un campo numérico en un hash
await redis.hincrby("user:123", "visits", 1);
// Incrementar un campo flotante en un hash
await redis.hincrbyfloat("user:123", "score", 1.5);Operaciones con Conjuntos
// Agregar miembro a un conjunto
await redis.sadd("tags", "javascript");
// Eliminar miembro de un conjunto
await redis.srem("tags", "javascript");
// Verificar si existe un miembro en un conjunto
const isMember = await redis.sismember("tags", "javascript");
// Obtener todos los miembros de un conjunto
const allTags = await redis.smembers("tags");
// Obtener un miembro aleatorio
const randomTag = await redis.srandmember("tags");
// Extraer (eliminar y devolver) un miembro aleatorio
const poppedTag = await redis.spop("tags");Pub/Sub
Bun proporciona bindings nativos para el protocolo Redis Pub/Sub. Nuevo en Bun 1.2.23
Uso Básico
Para comenzar a publicar mensajes, puedes configurar un publicador en publisher.ts:
import { RedisClient } from "bun";
const writer = new RedisClient("redis://localhost:6739");
await writer.connect();
writer.publish("general", "Hello everyone!");
writer.close();En otro archivo, crea el suscriptor en subscriber.ts:
import { RedisClient } from "bun";
const listener = new RedisClient("redis://localhost:6739");
await listener.connect();
await listener.subscribe("general", (message, channel) => {
console.log(`Recibido: ${message}`);
});En una terminal, ejecuta tu suscriptor:
bun run subscriber.tsy, en otra, ejecuta tu publicador:
bun run publisher.tsNOTE
El modo de suscripción toma el control de la conexión `RedisClient`. Un cliente con suscripciones solo puede llamar a `RedisClient.prototype.subscribe()`. En otras palabras, las aplicaciones que necesitan enviar mensajes a Redis requieren una conexión separada, obtenible mediante `.duplicate()`: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");Publicar
La publicación de mensajes se realiza mediante el método publish():
await client.publish(channelName, message);Suscripciones
El RedisClient de Bun te permite suscribirte a canales mediante el método .subscribe():
await client.subscribe(channel, (message, channel) => {});Puedes cancelar la suscripción mediante el método .unsubscribe():
await client.unsubscribe(); // Cancelar suscripción de todos los canales.
await client.unsubscribe(channel); // Cancelar suscripción de un canal específico.
await client.unsubscribe(channel, listener); // Cancelar suscripción de un oyente específico.Uso Avanzado
Ejecución de Comandos y Pipeline
El cliente automáticamente pone en pipeline los comandos, mejorando el rendimiento al enviar múltiples comandos en un lote y procesar las respuestas a medida que llegan.
// Los comandos se ponen en pipeline automáticamente por defecto
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);Para deshabilitar el pipeline automático, puedes establecer la opción enableAutoPipelining en false:
const client = new RedisClient("redis://localhost:6379", {
enableAutoPipelining: false,
});Comandos Crudos
Cuando necesitas usar comandos que no tienen métodos de conveniencia, puedes usar el método send:
// Ejecutar cualquier comando de Redis
const info = await redis.send("INFO", []);
// LPUSH a una lista
await redis.send("LPUSH", ["mylist", "value1", "value2"]);
// Obtener rango de lista
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);El método send te permite usar cualquier comando de Redis, incluso aquellos que no tienen métodos dedicados en el cliente. El primer argumento es el nombre del comando y el segundo argumento es un array de argumentos de cadena.
Eventos de Conexión
Puedes registrar manejadores para eventos de conexión:
const client = new RedisClient();
// Se llama cuando se conecta exitosamente al servidor Redis
client.onconnect = () => {
console.log("Conectado al servidor Redis");
};
// Se llama cuando se desconecta del servidor Redis
client.onclose = error => {
console.error("Desconectado del servidor Redis:", error);
};
// Conectar/desconectar manualmente
await client.connect();
client.close();Estado de la Conexión y Monitoreo
// Verificar si está conectado
console.log(client.connected); // booleano que indica el estado de la conexión
// Verificar la cantidad de datos en búfer (en bytes)
console.log(client.bufferedAmount);Conversión de Tipos
El cliente Redis maneja automáticamente la conversión de tipos para las respuestas de Redis:
- Las respuestas enteras se devuelven como números de JavaScript
- Las cadenas bulk se devuelven como cadenas de JavaScript
- Las cadenas simples se devuelven como cadenas de JavaScript
- Las cadenas bulk null se devuelven como
null - Las respuestas de array se devuelven como arrays de JavaScript
- Las respuestas de error lanzan errores de JavaScript con códigos de error apropiados
- Las respuestas booleanas (RESP3) se devuelven como booleanos de JavaScript
- Las respuestas de mapa (RESP3) se devuelven como objetos de JavaScript
- Las respuestas de conjunto (RESP3) se devuelven como arrays de JavaScript
Manejo especial para comandos específicos:
EXISTSdevuelve un booleano en lugar de un número (1 se convierte en true, 0 se convierte en false)SISMEMBERdevuelve un booleano (1 se convierte en true, 0 se convierte en false)
Los siguientes comandos deshabilitan el pipeline automático:
AUTHINFOQUITEXECMULTIWATCHSCRIPTSELECTCLUSTERDISCARDUNWATCHPIPELINESUBSCRIBEUNSUBSCRIBEUNPSUBSCRIBE
Opciones de Conexión
Al crear un cliente, puedes pasar varias opciones para configurar la conexión:
const client = new RedisClient("redis://localhost:6379", {
// Tiempo de espera de conexión en milisegundos (predeterminado: 10000)
connectionTimeout: 5000,
// Tiempo de espera de inactividad en milisegundos (predeterminado: 0 = sin tiempo de espera)
idleTimeout: 30000,
// Si se debe reconectar automáticamente al desconectarse (predeterminado: true)
autoReconnect: true,
// Número máximo de intentos de reconexión (predeterminado: 10)
maxRetries: 10,
// Si se deben poner en cola los comandos cuando está desconectado (predeterminado: true)
enableOfflineQueue: true,
// Si se deben poner en pipeline automáticamente los comandos (predeterminado: true)
enableAutoPipelining: true,
// Opciones TLS (predeterminado: false)
tls: true,
// Alternativamente, proporciona configuración TLS personalizada:
// tls: {
// rejectUnauthorized: true,
// ca: "path/to/ca.pem",
// cert: "path/to/cert.pem",
// key: "path/to/key.pem",
// }
});Comportamiento de Reconexión
Cuando se pierde una conexión, el cliente intenta automáticamente reconectar con retroceso exponencial:
- El cliente comienza con un pequeño retraso (50ms) y lo duplica con cada intento
- El retraso de reconexión está limitado a 2000ms (2 segundos)
- El cliente intenta reconectar hasta
maxRetriesveces (predeterminado: 10) - Los comandos ejecutados durante la desconexión:
- Se ponen en cola si
enableOfflineQueuees true (predeterminado) - Se rechazan inmediatamente si
enableOfflineQueuees false
- Se ponen en cola si
Formatos de URL Soportados
El cliente Redis soporta varios formatos de URL:
// URL estándar de Redis
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");
// Con autenticación
new RedisClient("redis://username:password@localhost:6379");
// Con número de base de datos
new RedisClient("redis://localhost:6379/0");
// Conexiones TLS
new RedisClient("rediss://localhost:6379");
new RedisClient("rediss://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
// Conexiones de socket Unix
new RedisClient("redis+unix:///path/to/socket");
new RedisClient("redis+unix:///path/to/socket");
// TLS sobre socket Unix
new RedisClient("redis+tls+unix:///path/to/socket");
new RedisClient("redis+tls+unix:///path/to/socket");Manejo de Errores
El cliente Redis lanza errores tipados para diferentes escenarios:
try {
await redis.get("non-existent-key");
} catch (error) {
if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
console.error("La conexión al servidor Redis fue cerrada");
} else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
console.error("Autenticación fallida");
} else {
console.error("Error inesperado:", error);
}
}Códigos de error comunes:
ERR_REDIS_CONNECTION_CLOSED- La conexión al servidor fue cerradaERR_REDIS_AUTHENTICATION_FAILED- Falló la autenticación con el servidorERR_REDIS_INVALID_RESPONSE- Se recibió una respuesta inválida del servidor
Ejemplos de Casos de Uso
Caché
async function getUserWithCache(userId) {
const cacheKey = `user:${userId}`;
// Intentar obtener del caché primero
const cachedUser = await redis.get(cacheKey);
if (cachedUser) {
return JSON.parse(cachedUser);
}
// No está en caché, obtener de la base de datos
const user = await database.getUser(userId);
// Almacenar en caché por 1 hora
await redis.set(cacheKey, JSON.stringify(user));
await redis.expire(cacheKey, 3600);
return user;
}Limitación de Tasa
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
const key = `ratelimit:${ip}`;
// Incrementar contador
const count = await redis.incr(key);
// Establecer expiración si esta es la primera solicitud en la ventana
if (count === 1) {
await redis.expire(key, windowSecs);
}
// Verificar si se excedió el límite
return {
limited: count > limit,
remaining: Math.max(0, limit - count),
};
}Almacenamiento de Sesiones
async function createSession(userId, data) {
const sessionId = crypto.randomUUID();
const key = `session:${sessionId}`;
// Almacenar sesión con expiración
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}`;
// Obtener datos de sesión
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 Implementación
El cliente Redis de Bun está implementado en Zig y usa el Protocolo de Serialización de Redis (RESP3). Gestiona las conexiones de manera eficiente y proporciona reconexión automática con retroceso exponencial.
El cliente soporta pipeline de comandos, lo que significa que se pueden enviar múltiples comandos sin esperar las respuestas de los comandos anteriores. Esto mejora significativamente el rendimiento al enviar múltiples comandos en sucesión.
Limitaciones y Planes Futuros
Limitaciones actuales del cliente Redis que planeamos abordar en versiones futuras:
- Las transacciones (MULTI/EXEC) deben hacerse mediante comandos crudos por ahora
Funciones no soportadas:
- Redis Sentinel
- Redis Cluster