Bun's Built-in Redis Client: Fast, Simple, Production-Ready

Redis is the backbone of modern applications—powering caching, real-time features, pub/sub messaging, and rate limiting. With Bun 1.3, you now have a first-class, built-in Redis client that's incredibly fast, fully typed, and production-ready out of the box.

No more installing node-redis, ioredis, or configuring connection pools. Just import and go. Let's dive into what makes Bun's Redis client special and how to use it effectively.

Zero-Configuration Setup

Getting started with Bun's Redis client couldn't be simpler. The default client automatically reads from your environment variables:

import { redis } from "bun";

// Reads from REDIS_URL or VALKEY_URL
// Defaults to redis://localhost:6379
await redis.set("greeting", "Hello from Bun!");
const value = await redis.get("greeting"); // "Hello from Bun!"
console.log(value);

That's it. No connection setup, no pool configuration, no callbacks. Just clean, Promise-based API that works immediately.

Environment Variables

Bun looks for these environment variables (in order of priority):

  1. REDIS_URL - Standard Redis connection string
  2. VALKEY_URL - For Valkey (Redis fork) connections
  3. Falls back to redis://localhost:6379

Example connection strings:

# Standard
REDIS_URL=redis://localhost:6379

# With authentication
REDIS_URL=redis://username:password@localhost:6379

# With database number
REDIS_URL=redis://localhost:6379/0

# TLS connection
REDIS_URL=rediss://localhost:6379

# Unix socket
REDIS_URL=redis+unix:///var/run/redis.sock

Comprehensive Command Support

Bun's Redis client supports 66 commands out of the box, covering the most common use cases:

String Operations

// Basic get/set
await redis.set("user:1:name", "Alice");
const name = await redis.get("user:1:name"); // "Alice"

// Expiration
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // 1 hour TTL
const ttl = await redis.ttl("session:123"); // 3600

// Binary data
const buffer = await redis.getBuffer("user:1:name"); // Buffer<Uint8Array>

Numeric Operations

await redis.set("counter", "0");

// Increment
await redis.incr("counter"); // 1
await redis.incrby("counter", 5); // 6

// Decrement
await redis.decr("counter"); // 5
await redis.decrby("counter", 2); // 3

Hash Operations (HSET, HGET, HMGET, HMSET, HINCRBY, etc.)

// Set multiple fields
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com"]);

// Get specific fields
const userFields = await redis.hmget("user:123", ["name", "email"]);
// ["Alice", "alice@example.com"]

// Get single field
const userName = await redis.hget("user:123", "name"); // "Alice"

// Increment hash field
await redis.hincrby("user:123", "visits", 1); // 1
await redis.hincrbyfloat("user:123", "score", 1.5); // 1.5

Set Operations (SADD, SREM, SMEMBERS, SISMEMBER, etc.)

// Add members
await redis.sadd("tags", "javascript", "typescript", "bun");

// Check membership
const isMember = await redis.sismember("tags", "javascript"); // true

// Get all members
const allTags = await redis.smembers("tags"); // ["javascript", "typescript", "bun"]

// Remove member
await redis.srem("tags", "typescript");

List Operations (LPUSH, LRANGE, etc.)

// Push to list
await redis.lpush("messages", "hello", "world");

// Get range
const messages = await redis.lrange("messages", 0, -1); // ["world", "hello"]

Pub/Sub Messaging

Real-time features like notifications, chat, and live updates rely on Redis pub/sub. Bun's client makes this straightforward:

import { RedisClient } from "bun";

// Create separate clients for publisher and subscriber
const publisher = new RedisClient("redis://localhost:6379");
const subscriber = new RedisClient("redis://localhost:6379");

// Subscribe to a channel
await subscriber.subscribe("notifications", (message, channel) => {
  console.log(`[${channel}] Received: ${message}`);
});

// Publish messages
await publisher.publish("notifications", "Hello from Bun!");
await publisher.publish("notifications", "Another message");

The callback receives both the message content and the channel name, making it easy to handle multiple channels:

// Subscribe to multiple channels
await subscriber.subscribe("alerts", handleAlert);
await subscriber.subscribe("updates", handleUpdate);

function handleAlert(message: string, channel: string) {
  console.log(`🚨 [${channel}] ${message}`);
}

function handleUpdate(message: string, channel: string) {
  console.log(`📢 [${channel}] ${message}`);
}

Performance: Built for Speed

Bun's Redis client is implemented in Zig using the Redis Serialization Protocol (RESP3), giving it significant performance advantages over popular JavaScript clients like ioredis.

Automatic Pipelining

Commands are automatically batched and pipelined, reducing network round-trips:

// These commands are automatically pipelined
await redis.set("key1", "value1");
await redis.set("key2", "value2");
await redis.set("key3", "value3");
const values = await redis.mget(["key1", "key2", "key3"]);

As batch size grows, Bun's performance advantage over ioredis increases significantly. This is particularly beneficial for:

  • Bulk data imports
  • Cache warming
  • Batch updates
  • Multi-key operations

Native Zig Implementation

The client is implemented directly in Zig, not JavaScript, which means:

  • Zero JavaScript overhead for protocol parsing
  • Direct memory access for binary data
  • Efficient type conversion between Redis and JavaScript types

Type Conversion

Redis types map cleanly to JavaScript types:

Redis TypeJavaScript Type
Integernumber
Bulk Stringstring
Nullnull
ArrayArray
Boolean (RESP3)boolean
Map (RESP3)Object
Set (RESP3)Array

Production-Ready Reliability

Bun's Redis client includes built-in features for production environments:

Automatic Reconnection

const client = new RedisClient("redis://localhost:6379", {
  autoReconnect: true,    // Default: true
  maxRetries: 10,         // Default: 10
});

// Connection drops are handled automatically with exponential backoff
// Starting at 50ms, doubling to max 2000ms

Connection Timeouts

const client = new RedisClient("redis://localhost:6379", {
  connectionTimeout: 5000,  // Default: 10000ms
  idleTimeout: 30000,       // Default: 0 (no timeout)
});

Offline Queue

When connection is lost, commands are queued and executed once reconnected:

const client = new RedisClient("redis://localhost:6379", {
  enableOfflineQueue: true,  // Default: true
});

// These commands will queue if disconnected
// and execute when connection is restored
await redis.set("key", "value");

Auto-Pipelining

Commands are automatically pipelined for improved throughput:

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

Real-World Use Cases

Caching Layer

async function getUserWithCache(userId: string) {
  const cacheKey = `user:${userId}`;
  const cachedUser = await redis.get(cacheKey);

  if (cachedUser) {
    return JSON.parse(cachedUser);
  }

  // Cache miss - fetch from database
  const user = await database.getUser(userId);

  // Cache for 1 hour
  await redis.set(cacheKey, JSON.stringify(user));
  await redis.expire(cacheKey, 3600);

  return user;
}

Rate Limiting

async function rateLimit(
  ip: string,
  limit: number = 100,
  windowSecs: number = 3600
): Promise<{ limited: boolean; remaining: number }> {
  const key = `ratelimit:${ip}`;

  // Increment counter
  const count = await redis.incr(key);

  // Set expiration on first request
  if (count === 1) {
    await redis.expire(key, windowSecs);
  }

  const remaining = Math.max(0, limit - count);
  const limited = count > limit;

  return { limited, remaining };
}

// Usage
const result = await rateLimit("192.168.1.1", 100, 3600);
if (result.limited) {
  throw new Error("Rate limit exceeded");
}

Distributed Lock

async function acquireLock(
  key: string,
  ttl: number = 30000
): Promise<boolean> {
  // Use SET with NX (only set if not exists) and PX (expiration in milliseconds)
  const result = await redis.send("SET", [key, "locked", "NX", "PX", ttl]);
  return result === "OK";
}

async function releaseLock(key: string): Promise<void> {
  await redis.del(key);
}

// Usage
const acquired = await acquireLock("resource:123", 30000);
if (acquired) {
  try {
    // Perform work
    await processResource(123);
  } finally {
    await releaseLock("resource:123");
  }
}

Session Storage

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

  await redis.hset(sessionKey, ["userId", userId, "data", JSON.stringify(data)]);
  await redis.expire(sessionKey, 86400); // 24 hours

  return sessionId;
}

async function getSession(sessionId: string) {
  const sessionKey = `session:${sessionId}`;
  const session = await redis.hgetall(sessionKey);

  if (!session) {
    return null;
  }

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

Leaderboard with Sorted Sets

async function updateScore(userId: string, score: number) {
  await redis.zadd("leaderboard", score, userId);
}

async function getTopPlayers(limit: number = 10) {
  return await redis.zrevrange("leaderboard", 0, limit - 1, "WITHSCORES");
}

async function getUserRank(userId: string) {
  const rank = await redis.zrevrank("leaderboard", userId);
  return rank !== null ? rank + 1 : null; // 0-indexed to 1-indexed
}

Advanced Features

Raw Commands

For commands not directly exposed, use send():

// Server info
const info = await redis.send("INFO", []);

// List operations
await redis.send("LPUSH", ["mylist", "value1", "value2"]);
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);

// Sorted sets
await redis.send("ZADD", ["leaderboard", "100", "user1", "200", "user2"]);

Custom Client Configuration

import { RedisClient } from "bun";

const client = new RedisClient("redis://localhost:6379", {
  connectionTimeout: 5000,
  idleTimeout: 30000,
  autoReconnect: true,
  maxRetries: 10,
  enableOfflineQueue: true,
  enableAutoPipelining: true,
});

// Explicit connection control
await client.connect();
await client.set("key", "value");
client.close();

TLS Connections

// Using rediss:// scheme
const tlsClient = new RedisClient("rediss://localhost:6379");

// Or with explicit TLS config
const customTlsClient = new RedisClient("redis://localhost:6379", {
  tls: {
    rejectUnauthorized: false, // For self-signed certs
    // ... other TLS options
  },
});

Current Limitations

Bun's Redis client is production-ready for most use cases, but has some current limitations:

  • Redis Sentinel: Not yet supported
  • Redis Cluster: Not yet supported (use single-node Redis)
  • Transactions: Must use raw MULTI/EXEC commands

For transactions:

// Use raw commands for transactions
await redis.send("MULTI", []);
await redis.send("SET", ["key1", "value1"]);
await redis.send("SET", ["key2", "value2"]);
const result = await redis.send("EXEC", []);

Check the official Bun Redis documentation for the latest supported commands and features.

Migration from Other Clients

From ioredis

// Before (ioredis)
import Redis from "ioredis";
const redis = new Redis();

// After (Bun)
import { redis } from "bun";
// API is nearly identical!

From node-redis

// Before (node-redis)
import { createClient } from "redis";
const client = createClient();
await client.connect();
await client.set("key", "value");

// After (Bun)
import { redis } from "bun";
await redis.set("key", "value");

The API is Promise-based and intuitive, making migration straightforward.

Conclusion

Bun's built-in Redis client delivers:

  • Zero configuration - Works out of the box with environment variables
  • 66 commands - Covers all common use cases
  • Production-ready - Auto-reconnect, timeouts, queuing, pipelining
  • Blazing fast - Native Zig implementation with automatic pipelining
  • Simple API - Clean, Promise-based, fully typed

Whether you're building a cache, implementing rate limiting, or powering real-time features with pub/sub, Bun's Redis client has you covered. Drop it into your next Bun application and experience the speed and simplicity firsthand.

For more details, visit the official Bun Redis documentation.