Skip to content

NOTE

Le client Redis de Bun prend en charge les versions 7.2 et supérieures du serveur Redis.

Bun fournit des liaisons natives pour travailler avec les bases de données Redis avec une API moderne basée sur les Promesses. L'interface est conçue pour être simple et performante, avec une gestion de connexion intégrée, des réponses entièrement typées et une prise en charge de TLS.

ts
import { redis } from "bun";

// Définir une clé
await redis.set("greeting", "Hello from Bun!");

// Récupérer une clé
const greeting = await redis.get("greeting");
console.log(greeting); // "Hello from Bun!"

// Incrémenter un compteur
await redis.set("counter", 0);
await redis.incr("counter");

// Vérifier si une clé existe
const exists = await redis.exists("greeting");

// Supprimer une clé
await redis.del("greeting");

Démarrage

Pour utiliser le client Redis, vous devez d'abord créer une connexion :

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

// Utilisation du client par défaut (lit les informations de connexion depuis l'environnement)
// process.env.REDIS_URL est utilisé par défaut
await redis.set("hello", "world");
const result = await redis.get("hello");

// Création d'un client personnalisé
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");

Par défaut, le client lit les informations de connexion depuis les variables d'environnement suivantes (par ordre de priorité) :

  • REDIS_URL
  • VALKEY_URL
  • Si non défini, la valeur par défaut est "redis://localhost:6379"

Cycle de vie de la connexion

Le client Redis gère automatiquement les connexions en arrière-plan :

ts
// Aucune connexion n'est établie tant qu'une commande n'est pas exécutée
const client = new RedisClient();

// La première commande initialise la connexion
await client.set("key", "value");

// La connexion reste ouverte pour les commandes suivantes
await client.get("key");

// Fermer explicitement la connexion lorsque terminé
client.close();

Vous pouvez également contrôler manuellement le cycle de vie de la connexion :

ts
const client = new RedisClient();

// Se connecter explicitement
await client.connect();

// Exécuter des commandes
await client.set("key", "value");

// Se déconnecter lorsque terminé
client.close();

Opérations de base

Opérations sur les chaînes

ts
// Définir une clé
await redis.set("user:1:name", "Alice");

// Récupérer une clé
const name = await redis.get("user:1:name");

// Récupérer une clé comme Uint8Array
const buffer = await redis.getBuffer("user:1:name");

// Supprimer une clé
await redis.del("user:1:name");

// Vérifier si une clé existe
const exists = await redis.exists("user:1:name");

// Définir l'expiration (en secondes)
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // expire dans 1 heure

// Obtenir le temps de vie restant (en secondes)
const ttl = await redis.ttl("session:123");

Opérations numériques

ts
// Définir la valeur initiale
await redis.set("counter", "0");

// Incrémenter de 1
await redis.incr("counter");

// Décrémenter de 1
await redis.decr("counter");

Opérations sur les hachages

ts
// Définir plusieurs champs dans un hachage
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);

// Récupérer plusieurs champs depuis un hachage
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]

// Récupérer un seul champ depuis un hachage (retourne la valeur directement, null si manquant)
const userName = await redis.hget("user:123", "name");
console.log(userName); // "Alice"

// Incrémenter un champ numérique dans un hachage
await redis.hincrby("user:123", "visits", 1);

// Incrémenter un champ flottant dans un hachage
await redis.hincrbyfloat("user:123", "score", 1.5);

Opérations sur les ensembles

ts
// Ajouter un membre à un ensemble
await redis.sadd("tags", "javascript");

// Supprimer un membre d'un ensemble
await redis.srem("tags", "javascript");

// Vérifier si un membre existe dans un ensemble
const isMember = await redis.sismember("tags", "javascript");

// Récupérer tous les membres d'un ensemble
const allTags = await redis.smembers("tags");

// Récupérer un membre aléatoire
const randomTag = await redis.srandmember("tags");

// Pop (supprimer et retourner) un membre aléatoire
const poppedTag = await redis.spop("tags");

Pub/Sub

Bun fournit des liaisons natives pour le protocole Redis Pub/Sub. Nouveau dans Bun 1.2.23

Utilisation de base

Pour commencer à publier des messages, vous pouvez configurer un éditeur dans publisher.ts :

typescript
import { RedisClient } from "bun";

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

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

writer.close();

Dans un autre fichier, créez l'abonné dans 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(`Reçu : ${message}`);
});

Dans un shell, exécutez votre abonné :

bash
bun run subscriber.ts

et, dans un autre, exécutez votre éditeur :

bash
bun run publisher.ts

NOTE

Le mode d'abonnement prend le contrôle de la connexion `RedisClient`. Un client avec des abonnements ne peut appeler que `RedisClient.prototype.subscribe()`. En d'autres termes, les applications qui doivent envoyer des messages à Redis ont besoin d'une connexion séparée, obtenable via `.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");

Publication

La publication de messages se fait via la méthode publish() :

typescript
await client.publish(channelName, message);

Abonnements

Le RedisClient de Bun vous permet de vous abonner à des canaux via la méthode .subscribe() :

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

Vous pouvez vous désabonner via la méthode .unsubscribe() :

typescript
await client.unsubscribe(); // Se désabonner de tous les canaux.
await client.unsubscribe(channel); // Se désabonner d'un canal particulier.
await client.unsubscribe(channel, listener); // Se désabonner d'un écouteur particulier.

Utilisation avancée

Exécution de commandes et pipelining

Le client pipeline automatiquement les commandes, améliorant les performances en envoyant plusieurs commandes par lot et en traitant les réponses au fur et à mesure de leur arrivée.

ts
// Les commandes sont automatiquement pipelinées par défaut
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);

Pour désactiver le pipelining automatique, vous pouvez définir l'option enableAutoPipelining à false :

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

Commandes brutes

Lorsque vous devez utiliser des commandes qui n'ont pas de méthodes de commodité, vous pouvez utiliser la méthode send :

ts
// Exécuter n'importe quelle commande Redis
const info = await redis.send("INFO", []);

// LPUSH vers une liste
await redis.send("LPUSH", ["mylist", "value1", "value2"]);

// Récupérer une plage de liste
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);

La méthode send vous permet d'utiliser n'importe quelle commande Redis, même celles qui n'ont pas de méthodes dédiées dans le client. Le premier argument est le nom de la commande, et le deuxième argument est un tableau d'arguments de type chaîne.

Événements de connexion

Vous pouvez enregistrer des gestionnaires pour les événements de connexion :

ts
const client = new RedisClient();

// Appelé lors de la connexion réussie au serveur Redis
client.onconnect = () => {
  console.log("Connecté au serveur Redis");
};

// Appelé lors de la déconnexion du serveur Redis
client.onclose = error => {
  console.error("Déconnecté du serveur Redis :", error);
};

// Connecter/déconnecter manuellement
await client.connect();
client.close();

État et surveillance de la connexion

ts
// Vérifier si connecté
console.log(client.connected); // booléen indiquant l'état de connexion

// Vérifier la quantité de données en mémoire tampon (en octets)
console.log(client.bufferedAmount);

Conversion de type

Le client Redis gère automatiquement la conversion de type pour les réponses Redis :

  • Les réponses entières sont retournées comme des nombres JavaScript
  • Les chaînes bulk sont retournées comme des chaînes JavaScript
  • Les chaînes simples sont retournées comme des chaînes JavaScript
  • Les chaînes bulk nulles sont retournées comme null
  • Les réponses de type tableau sont retournées comme des tableaux JavaScript
  • Les réponses d'erreur lancent des erreurs JavaScript avec des codes d'erreur appropriés
  • Les réponses booléennes (RESP3) sont retournées comme des booléens JavaScript
  • Les réponses de type map (RESP3) sont retournées comme des objets JavaScript
  • Les réponses de type set (RESP3) sont retournées comme des tableaux JavaScript

Traitement spécial pour certaines commandes :

  • EXISTS retourne un booléen au lieu d'un nombre (1 devient true, 0 devient false)
  • SISMEMBER retourne un booléen (1 devient true, 0 devient false)

Les commandes suivantes désactivent le pipelining automatique :

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

Options de connexion

Lors de la création d'un client, vous pouvez passer diverses options pour configurer la connexion :

ts
const client = new RedisClient("redis://localhost:6379", {
  // Délai d'attente de connexion en millisecondes (par défaut : 10000)
  connectionTimeout: 5000,

  // Délai d'inactivité en millisecondes (par défaut : 0 = pas de délai)
  idleTimeout: 30000,

  // S'il faut se reconnecter automatiquement en cas de déconnexion (par défaut : true)
  autoReconnect: true,

  // Nombre maximum de tentatives de reconnexion (par défaut : 10)
  maxRetries: 10,

  // S'il faut mettre en file d'attente les commandes en cas de déconnexion (par défaut : true)
  enableOfflineQueue: true,

  // S'il faut automatiquement pipeliner les commandes (par défaut : true)
  enableAutoPipelining: true,

  // Options TLS (par défaut : false)
  tls: true,
  // Alternativement, fournir une configuration TLS personnalisée :
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   cert: "path/to/cert.pem",
  //   key: "path/to/key.pem",
  // }
});

Comportement de reconnexion

Lorsqu'une connexion est perdue, le client tente automatiquement de se reconnecter avec un backoff exponentiel :

  1. Le client commence avec un petit délai (50ms) et le double à chaque tentative
  2. Le délai de reconnexion est plafonné à 2000ms (2 secondes)
  3. Le client tente de se reconnecter jusqu'à maxRetries fois (par défaut : 10)
  4. Les commandes exécutées pendant la déconnexion sont :
    • Mises en file d'attente si enableOfflineQueue est true (par défaut)
    • Rejetées immédiatement si enableOfflineQueue est false

Formats d'URL pris en charge

Le client Redis prend en charge divers formats d'URL :

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

// Avec authentification
new RedisClient("redis://username:password@localhost:6379");

// Avec numéro de base de données
new RedisClient("redis://localhost:6379/0");

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

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

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

Gestion des erreurs

Le client Redis lance des erreurs typées pour différents scénarios :

ts
try {
  await redis.get("non-existent-key");
} catch (error) {
  if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
    console.error("La connexion au serveur Redis a été fermée");
  } else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
    console.error("Échec de l'authentification");
  } else {
    console.error("Erreur inattendue :", error);
  }
}

Codes d'erreur courants :

  • ERR_REDIS_CONNECTION_CLOSED - La connexion au serveur a été fermée
  • ERR_REDIS_AUTHENTICATION_FAILED - Échec de l'authentification avec le serveur
  • ERR_REDIS_INVALID_RESPONSE - Réponse invalide reçue du serveur

Exemples de cas d'utilisation

Mise en cache

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

  // Essayer de récupérer depuis le cache d'abord
  const cachedUser = await redis.get(cacheKey);
  if (cachedUser) {
    return JSON.parse(cachedUser);
  }

  // Pas dans le cache, récupérer depuis la base de données
  const user = await database.getUser(userId);

  // Stocker dans le cache pendant 1 heure
  await redis.set(cacheKey, JSON.stringify(user));
  await redis.expire(cacheKey, 3600);

  return user;
}

Limitation de débit

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

  // Incrémenter le compteur
  const count = await redis.incr(key);

  // Définir l'expiration si c'est la première requête dans la fenêtre
  if (count === 1) {
    await redis.expire(key, windowSecs);
  }

  // Vérifier si la limite est dépassée
  return {
    limited: count > limit,
    remaining: Math.max(0, limit - count),
  };
}

Stockage de session

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

  // Stocker la session avec expiration
  await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
  await redis.expire(key, 86400); // 24 heures

  return sessionId;
}

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

  // Récupérer les données de session
  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),
  };
}

Notes d'implémentation

Le client Redis de Bun est implémenté en Zig et utilise le protocole de sérialisation Redis (RESP3). Il gère les connexions efficacement et fournit une reconnexion automatique avec backoff exponentiel.

Le client prend en charge le pipelining des commandes, ce qui signifie que plusieurs commandes peuvent être envoyées sans attendre les réponses des commandes précédentes. Cela améliore considérablement les performances lors de l'envoi de plusieurs commandes successives.

Limitations et plans futurs

Limitations actuelles du client Redis que nous prévoyons de corriger dans les versions futures :

  • Les transactions (MULTI/EXEC) doivent être effectuées via des commandes brutes pour l'instant

Fonctionnalités non prises en charge :

  • Redis Sentinel
  • Redis Cluster

Bun édité par www.bunjs.com.cn