Skip to main content

Redis KV Driver

@zeltjs/kv ships a Redis backend through its @zeltjs/kv/adaptor-redis entry point. RedisKVAdaptor implements AtomicKVAdaptor on top of ioredis, supporting atomic operations like incr and setnx.

Installation

pnpm add @zeltjs/kv @zeltjs/redis

Peer dependency:

pnpm add @zeltjs/core

Basic Setup

Inject RedisKVAdaptor and create a namespaced store. namespace() returns an AtomicKVStore directly, and get() resolves to the value (or undefined when the key is missing) — there is no result wrapper to unwrap:

@Injectable()
export class CacheService {
  private store: AtomicKVStore;

  constructor(kv = inject(RedisKVAdaptor)) {
    this.store = kv.namespace('cache:');
  }

  async get<T>(key: string): Promise<T | undefined> {
    return this.store.get<T>(key);
  }

  async set<T extends Defined>(key: string, value: T, ttlSec?: number): Promise<void> {
    await this.store.set(key, value, { ttlSec });
  }
}

Register RedisConfig and RedisKVAdaptor when creating the app. RedisConfig provides the connection settings (consumed by RedisService, which RedisKVAdaptor depends on), so listing RedisKVAdaptor in injectables is enough — its dependencies resolve automatically:

const app = createApp([http({
    controllers: [AppController],
  })], { configs: [RedisConfig] });

By default, RedisConfig reads the connection URL from the REDIS_URL environment variable, falling back to redis://localhost:6379.

Custom Configuration

Extend RedisConfig to customize connection settings. The options getter returns ioredis RedisOptions:

@Config
class CustomRedisConfig extends RedisConfig {
  override get url(): string {
    return this.env.getString('REDIS_URL', 'redis://localhost:6379');
  }

  override get options() {
    return {
      maxRetriesPerRequest: 3,
      retryStrategy: (times: number) => Math.min(times * 100, 3000),
    };
  }
}

Register your custom config instead of the default:

const app = createApp([http({
    controllers: [AppController],
  })], { configs: [CustomRedisConfig] });

API Reference

RedisKVAdaptor

MethodDescription
namespace(prefix)Returns a namespaced AtomicKVStore

RedisKVAdaptor participates in the application lifecycle. The underlying ioredis connection is owned by RedisService and is disconnected automatically on shutdown (see Graceful Shutdown).

AtomicKVStore Methods

MethodDescription
get<T>(key)Retrieve a value, or undefined if missing
set<T>(key, value, opts?)Store a value with optional TTL
del(key)Delete a key
has(key)Check if key exists
expire(key, ttlSec)Update TTL for an existing key
incr(key, by?, opts?)Atomic increment
setnx<T>(key, value, opts?)Set if not exists
namespace(prefix)Create nested namespace

Production Setup

For production deployments, configure connection pooling and retry behavior:

@Config
class ProductionRedisConfig extends RedisConfig {
  override get options() {
    return {
      maxRetriesPerRequest: 3,
      enableReadyCheck: true,
      retryStrategy: (times: number) => {
        if (times > 10) return null;
        return Math.min(times * 200, 5000);
      },
    };
  }
}

Graceful Shutdown

You do not need to disconnect Redis manually. RedisService registers itself with the lifecycle manager, so when the application shuts down it disconnects the ioredis client automatically.

With @zeltjs/adapter-node, onNode installs SIGINT/SIGTERM handlers that trigger this shutdown, and handle.shutdown() does the same:

const handle = await nodeApp.listen({ port: 3000 });

// Disconnects the server and runs lifecycle shutdown (Redis included)
await handle.shutdown();