Skip to content

NOTE

Il client Redis di Bun supporta le versioni del server Redis 7.2 e successive.

Bun fornisce binding nativi per lavorare con database Redis con un'API moderna basata su Promise. L'interfaccia è progettata per essere semplice e performante, con gestione delle connessioni integrata, risposte completamente tipizzate e supporto TLS.

ts
import { redis } from "bun";

// Imposta una chiave
await redis.set("greeting", "Ciao da Bun!");

// Ottieni una chiave
const greeting = await redis.get("greeting");
console.log(greeting); // "Ciao da Bun!"

// Incrementa un contatore
await redis.set("counter", 0);
await redis.incr("counter");

// Controlla se una chiave esiste
const exists = await redis.exists("greeting");

// Elimina una chiave
await redis.del("greeting");

Per Iniziare

Per usare il client Redis, devi prima creare una connessione:

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

// Usando il client predefinito (legge le informazioni di connessione dall'ambiente)
// process.env.REDIS_URL è usato di default
await redis.set("hello", "world");
const result = await redis.get("hello");

// Creazione di un client personalizzato
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");

Di default, il client legge le informazioni di connessione dalle seguenti variabili d'ambiente (in ordine di precedenza):

  • REDIS_URL
  • VALKEY_URL
  • Se non impostato, default a "redis://localhost:6379"

Ciclo di Vita della Connessione

Il client Redis gestisce automaticamente le connessioni in background:

ts
// Nessuna connessione viene stabilita finché non viene eseguito un comando
const client = new RedisClient();

// Il primo comando avvia la connessione
await client.set("key", "value");

// La connessione rimane aperta per i comandi successivi
await client.get("key");

// Chiudi esplicitamente la connessione quando hai finito
client.close();

Puoi anche controllare manualmente il ciclo di vita della connessione:

ts
const client = new RedisClient();

// Connetti esplicitamente
await client.connect();

// Esegui comandi
await client.set("key", "value");

// Disconnetti quando hai finito
client.close();

Operazioni di Base

Operazioni con Stringhe

ts
// Imposta una chiave
await redis.set("user:1:name", "Alice");

// Ottieni una chiave
const name = await redis.get("user:1:name");

// Ottieni una chiave come Uint8Array
const buffer = await redis.getBuffer("user:1:name");

// Elimina una chiave
await redis.del("user:1:name");

// Controlla se una chiave esiste
const exists = await redis.exists("user:1:name");

// Imposta scadenza (in secondi)
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // scade tra 1 ora

// Ottieni tempo di vita residuo (in secondi)
const ttl = await redis.ttl("session:123");

Operazioni Numeriche

ts
// Imposta valore iniziale
await redis.set("counter", "0");

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

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

Operazioni con Hash

ts
// Imposta più campi in un hash
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);

// Ottieni più campi da un hash
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]

// Ottieni un singolo campo dall'hash (restituisce il valore direttamente, null se mancante)
const userName = await redis.hget("user:123", "name");
console.log(userName); // "Alice"

// Incrementa un campo numerico in un hash
await redis.hincrby("user:123", "visits", 1);

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

Operazioni con Set

ts
// Aggiungi membro al set
await redis.sadd("tags", "javascript");

// Rimuovi membro dal set
await redis.srem("tags", "javascript");

// Controlla se un membro esiste nel set
const isMember = await redis.sismember("tags", "javascript");

// Ottieni tutti i membri di un set
const allTags = await redis.smembers("tags");

// Ottieni un membro casuale
const randomTag = await redis.srandmember("tags");

// Pop (rimuovi e restituisci) un membro casuale
const poppedTag = await redis.spop("tags");

Pub/Sub

Bun fornisce binding nativi per il protocollo Redis Pub/Sub. Nuovo in Bun 1.2.23

Utilizzo di Base

Per iniziare a pubblicare messaggi, puoi configurare un publisher in publisher.ts:

typescript
import { RedisClient } from "bun";

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

writer.publish("general", "Ciao a tutti!");

writer.close();

In un altro file, crea il subscriber in 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(`Ricevuto: ${message}`);
});

In una shell, esegui il tuo subscriber:

bash
bun run subscriber.ts

e, in un'altra, esegui il tuo publisher:

bash
bun run publisher.ts

NOTE

La modalità subscription prende il controllo della connessione `RedisClient`. Un client con subscription può chiamare solo `RedisClient.prototype.subscribe()`. In altre parole, le applicazioni che devono comunicare con Redis necessitano di una connessione separata, ottenibile tramite `.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");

Pubblicazione

La pubblicazione dei messaggi avviene tramite il metodo publish():

typescript
await client.publish(channelName, message);

Sottoscrizioni

Il RedisClient di Bun ti permette di sottoscriverti ai canali tramite il metodo .subscribe():

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

Puoi annullare la sottoscrizione tramite il metodo .unsubscribe():

typescript
await client.unsubscribe(); // Annulla sottoscrizione da tutti i canali.
await client.unsubscribe(channel); // Annulla sottoscrizione da un canale specifico.
await client.unsubscribe(channel, listener); // Annulla sottoscrizione da un listener specifico.

Utilizzo Avanzato

Esecuzione Comandi e Pipelining

Il client esegue automaticamente il pipelining dei comandi, migliorando le prestazioni inviando più comandi in batch ed elaborando le risposte man mano che arrivano.

ts
// I comandi sono automaticamente in pipeline di default
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);

Per disabilitare il pipelining automatico, puoi impostare l'opzione enableAutoPipelining a false:

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

Comandi Raw

Quando devi usare comandi che non hanno metodi di convenienza, puoi usare il metodo send:

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

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

// Ottieni intervallo lista
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);

Il metodo send ti permette di usare qualsiasi comando Redis, anche quelli che non hanno metodi dedicati nel client. Il primo argomento è il nome del comando, e il secondo argomento è un array di argomenti stringa.

Eventi di Connessione

Puoi registrare handler per eventi di connessione:

ts
const client = new RedisClient();

// Chiamato quando connesso con successo al server Redis
client.onconnect = () => {
  console.log("Connesso al server Redis");
};

// Chiamato quando disconnesso dal server Redis
client.onclose = error => {
  console.error("Disconnesso dal server Redis:", error);
};

// Connetti/disconnetti manualmente
await client.connect();
client.close();

Stato e Monitoraggio della Connessione

ts
// Controlla se connesso
console.log(client.connected); // booleano che indica lo stato della connessione

// Controlla quantità di dati in buffer (in byte)
console.log(client.bufferedAmount);

Conversione di Tipo

Il client Redis gestisce automaticamente la conversione di tipo per le risposte Redis:

  • Le risposte intere sono restituite come numeri JavaScript
  • Le stringhe bulk sono restituite come stringhe JavaScript
  • Le stringhe semplici sono restituite come stringhe JavaScript
  • Le stringhe bulk null sono restituite come null
  • Le risposte array sono restituite come array JavaScript
  • Le risposte di errore lanciano errori JavaScript con codici di errore appropriati
  • Le risposte booleane (RESP3) sono restituite come booleani JavaScript
  • Le risposte map (RESP3) sono restituite come oggetti JavaScript
  • Le risposte set (RESP3) sono restituite come array JavaScript

Gestione speciale per comandi specifici:

  • EXISTS restituisce un booleano invece di un numero (1 diventa true, 0 diventa false)
  • SISMEMBER restituisce un booleano (1 diventa true, 0 diventa false)

I seguenti comandi disabilitano il pipelining automatico:

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

Opzioni di Connessione

Quando crei un client, puoi passare varie opzioni per configurare la connessione:

ts
const client = new RedisClient("redis://localhost:6379", {
  // Timeout di connessione in millisecondi (default: 10000)
  connectionTimeout: 5000,

  // Timeout di inattività in millisecondi (default: 0 = nessun timeout)
  idleTimeout: 30000,

  // Se riconnettere automaticamente in caso di disconnessione (default: true)
  autoReconnect: true,

  // Numero massimo di tentativi di riconnessione (default: 10)
  maxRetries: 10,

  // Se mettere in coda i comandi quando disconnesso (default: true)
  enableOfflineQueue: true,

  // Se eseguire automaticamente il pipelining dei comandi (default: true)
  enableAutoPipelining: true,

  // Opzioni TLS (default: false)
  tls: true,
  // In alternativa, fornisci configurazione TLS personalizzata:
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   cert: "path/to/cert.pem",
  //   key: "path/to/key.pem",
  // }
});

Comportamento di Riconnessione

Quando una connessione viene persa, il client tenta automaticamente di riconnettersi con backoff esponenziale:

  1. Il client inizia con un piccolo ritardo (50ms) e lo raddoppia a ogni tentativo
  2. Il ritardo di riconnessione è limitato a 2000ms (2 secondi)
  3. Il client tenta di riconnettersi fino a maxRetries volte (default: 10)
  4. I comandi eseguiti durante la disconnessione sono:
    • Messi in coda se enableOfflineQueue è true (default)
    • Rifiutati immediatamente se enableOfflineQueue è false

Formati URL Supportati

Il client Redis supporta vari formati URL:

ts
// URL Redis standard
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");

// Con autenticazione
new RedisClient("redis://username:password@localhost:6379");

// Con numero di database
new RedisClient("redis://localhost:6379/0");

// Connessioni TLS
new RedisClient("rediss://localhost:6379");
new RedisClient("rediss://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
new RedisClient("redis+tls://localhost:6379");

// Connessioni socket Unix
new RedisClient("redis+unix:///path/to/socket");
new RedisClient("redis+unix:///path/to/socket");

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

Gestione Errori

Il client Redis lancia errori tipizzati per diversi scenari:

ts
try {
  await redis.get("non-existent-key");
} catch (error) {
  if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
    console.error("La connessione al server Redis è stata chiusa");
  } else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
    console.error("Autenticazione fallita");
  } else {
    console.error("Errore imprevisto:", error);
  }
}

Codici di errore comuni:

  • ERR_REDIS_CONNECTION_CLOSED - La connessione al server è stata chiusa
  • ERR_REDIS_AUTHENTICATION_FAILED - Autenticazione fallita con il server
  • ERR_REDIS_INVALID_RESPONSE - Ricevuta una risposta non valida dal server

Esempi di Casi d'Uso

Caching

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

  // Prova prima a ottenere dalla cache
  const cachedUser = await redis.get(cacheKey);
  if (cachedUser) {
    return JSON.parse(cachedUser);
  }

  // Non in cache, ottieni dal database
  const user = await database.getUser(userId);

  // Memorizza in cache per 1 ora
  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 contatore
  const count = await redis.incr(key);

  // Imposta scadenza se questa è la prima richiesta nella finestra
  if (count === 1) {
    await redis.expire(key, windowSecs);
  }

  // Controlla se il limite è stato superato
  return {
    limited: count > limit,
    remaining: Math.max(0, limit - count),
  };
}

Session Storage

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

  // Memorizza sessione con scadenza
  await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
  await redis.expire(key, 86400); // 24 ore

  return sessionId;
}

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

  // Ottieni dati sessione
  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),
  };
}

Note sull'Implementazione

Il client Redis di Bun è implementato in Zig e usa il Redis Serialization Protocol (RESP3). Gestisce le connessioni in modo efficiente e fornisce riconnessione automatica con backoff esponenziale.

Il client supporta il pipelining dei comandi, il che significa che più comandi possono essere inviati senza attendere le risposte ai comandi precedenti. Questo migliora significativamente le prestazioni quando si inviano più comandi in successione.

Limitazioni e Piani Futuri

Limitazioni attuali del client Redis che pianifichiamo di affrontare in versioni future:

  • Le transazioni (MULTI/EXEC) devono essere eseguite tramite comandi raw per ora

Funzionalità non supportate:

  • Redis Sentinel
  • Redis Cluster

Bun a cura di www.bunjs.com.cn