Redis
Powerful Redis abstraction supporting single instances and clusters via ioredis, with automatic JSON serialization, connection lifecycle callbacks, Pub/Sub with optional zlib compression, RedisJSON operations, and raw command execution.
Quick Reference
| Class | Extends | Use Case |
|---|---|---|
DefaultRedisHelper | BaseHelper | Base class with unified API for all Redis operations |
RedisHelper | DefaultRedisHelper | Single Redis instance with auto-connect and retry strategy |
RedisClusterHelper | DefaultRedisHelper | Redis cluster with multi-node support |
Import Paths
// From the helpers package
import {
DefaultRedisHelper,
RedisHelper,
RedisClusterHelper,
} from '@venizia/ignis-helpers';
// Types are also available from the core package (via type-only re-export)
import type {
IRedisHelperOptions,
IRedisClusterHelperOptions,
IRedisHelperCallbacks,
IRedisHelperProps,
IRedisClusterHelperProps,
} from '@venizia/ignis-helpers'; // or from '@venizia/ignis'Creating an Instance
Single Instance
Use RedisHelper for connecting to a single Redis server.
import { RedisHelper } from '@venizia/ignis-helpers';
const redis = new RedisHelper({
name: 'my-redis',
host: 'localhost',
port: 6379,
password: 'secret',
database: 0,
autoConnect: true,
maxRetry: 5,
onInitialized: ({ name, helper }) => {
console.log(`Redis "${name}" initialized`);
},
onConnected: ({ name }) => {
console.log(`Redis "${name}" connected`);
},
onReady: ({ name }) => {
console.log(`Redis "${name}" ready`);
},
onError: ({ name, error }) => {
console.error(`Redis "${name}" error:`, error);
},
});IRedisHelperOptions
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | -- | Unique identifier for this client instance |
host | string | Yes | -- | Redis server hostname |
port | string | number | Yes | -- | Redis server port |
password | string | Yes | -- | Redis password |
user | string | No | -- | Redis username (ACL authentication) |
database | number | No | 0 | Redis database index |
autoConnect | boolean | No | true | Connect immediately on creation. When false, uses ioredis lazyConnect mode |
maxRetry | number | No | 0 | Maximum reconnection attempts. 0 = unlimited retries. Values below 0 disable retry entirely |
onInitialized | (opts: { name: string; helper: DefaultRedisHelper }) => void | No | -- | Called synchronously immediately after client construction |
onConnected | (opts: { name: string; helper: DefaultRedisHelper }) => void | No | -- | Called when the TCP connection is established |
onReady | (opts: { name: string; helper: DefaultRedisHelper }) => void | No | -- | Called when the client is ready to accept commands |
onError | (opts: { name: string; helper: DefaultRedisHelper; error: any }) => void | No | -- | Called on connection or command errors |
Retry strategy: Exponential backoff clamped between 1s and 5s: Math.max(Math.min(attempt * 2000, 5000), 1000). The ioredis option maxRetriesPerRequest is set to null internally, which is required for compatibility with BullMQ.
ioredis configuration: RedisHelper creates an ioredis Redis instance with showFriendlyErrorStack: true for better error diagnostics.
Cluster
Use RedisClusterHelper for connecting to a Redis cluster.
import { RedisClusterHelper } from '@venizia/ignis-helpers';
const cluster = new RedisClusterHelper({
name: 'my-cluster',
nodes: [
{ host: 'redis-node-1', port: 7000 },
{ host: 'redis-node-2', port: 7001, password: 'node-specific-pass' },
{ host: 'redis-node-3', port: 7002 },
],
clusterOptions: {
redisOptions: { password: 'cluster-password' },
},
onReady: ({ name }) => {
console.log(`Cluster "${name}" ready`);
},
});IRedisClusterHelperOptions
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | -- | Unique identifier for this cluster client |
nodes | Array<{ host: string; port: string | number; password?: string }> | Yes | -- | List of cluster node addresses |
clusterOptions | ClusterOptions | No | -- | ioredis ClusterOptions passed directly to the Cluster constructor |
onInitialized | (opts: { name: string; helper: DefaultRedisHelper }) => void | No | -- | Called synchronously immediately after cluster client construction |
onConnected | (opts: { name: string; helper: DefaultRedisHelper }) => void | No | -- | Called when connected |
onReady | (opts: { name: string; helper: DefaultRedisHelper }) => void | No | -- | Called when ready |
onError | (opts: { name: string; helper: DefaultRedisHelper; error: any }) => void | No | -- | Called on errors |
Lifecycle Callbacks
All Redis helpers accept the same lifecycle callbacks via IRedisHelperCallbacks:
interface IRedisHelperCallbacks {
onInitialized?: (opts: { name: string; helper: DefaultRedisHelper }) => void;
onConnected?: (opts: { name: string; helper: DefaultRedisHelper }) => void;
onReady?: (opts: { name: string; helper: DefaultRedisHelper }) => void;
onError?: (opts: { name: string; helper: DefaultRedisHelper; error: any }) => void;
}| Callback | Redis Event | When |
|---|---|---|
onInitialized | -- | Immediately after client construction (synchronous) |
onConnected | connect | TCP connection established |
onReady | ready | Client ready to accept commands |
onError | error | Connection or command error |
Additionally, the client internally logs a warning on reconnecting events.
Usage
All operations below are available on both RedisHelper and RedisClusterHelper via the shared DefaultRedisHelper base class.
Connection Management
// Manual connect (when autoConnect is false)
const connected = await redis.connect();
// => true if status becomes 'ready', false if already connected/connecting
// Disconnect gracefully (sends QUIT command)
const disconnected = await redis.disconnect();
// => true if quit succeeded
// Health check
const pong = await redis.ping();
// => 'PONG'
// Access underlying ioredis client
const ioredisClient = redis.getClient();
// RedisHelper returns Redis, RedisClusterHelper returns ClusterNOTE
connect() resolves to false without action if the client status is already ready, reconnecting, or connecting. Similarly, disconnect() resolves to false if the status is end or close.
Key-Value Operations
// Set a value (auto-serialized to JSON)
await redis.set({ key: 'user:1', value: { name: 'Alice', age: 30 } });
// Set with logging enabled
await redis.set({ key: 'user:1', value: { name: 'Alice' }, options: { log: true } });
// Get raw string value
const raw = await redis.get({ key: 'user:1' });
// => '{"name":"Alice","age":30}'
// Get with custom transform
const parsed = await redis.get({
key: 'user:1',
transform: (input) => JSON.parse(input),
});
// Convenience: get as string (alias for get)
const str = await redis.getString({ key: 'user:1' });
// Convenience: get as parsed JSON object
const user = await redis.getObject({ key: 'user:1' });
// => { name: 'Alice', age: 30 }
// Delete keys
await redis.del({ keys: ['user:1', 'user:2'] });Multi-Key Operations
// Set multiple keys at once
await redis.mset({
payload: [
{ key: 'user:1', value: { name: 'Alice' } },
{ key: 'user:2', value: { name: 'Bob' } },
],
});
// Get multiple raw values
const values = await redis.mget({ keys: ['user:1', 'user:2'] });
// => ['{"name":"Alice"}', '{"name":"Bob"}']
// Get multiple with transform
const users = await redis.mget({
keys: ['user:1', 'user:2'],
transform: (el) => JSON.parse(el),
});
// Convenience: get multiple strings
const strings = await redis.getStrings({ keys: ['key1', 'key2'] });
// Convenience: get multiple parsed objects
const objects = await redis.getObjects({ keys: ['user:1', 'user:2'] });TIP
mSet(), mGet(), hSet(), and hGetAll() are camelCase aliases for mset(), mget(), hset(), and hgetall() respectively. Both forms are valid.
Hash Operations
// Set hash fields
await redis.hset({
key: 'session:abc',
value: { userId: 'u1', token: 'tok123', createdAt: '2025-01-01' },
});
// Set hash fields with logging
await redis.hset({
key: 'session:abc',
value: { userId: 'u1' },
options: { log: true },
});
// Get all hash fields
const session = await redis.hgetall({ key: 'session:abc' });
// => { userId: 'u1', token: 'tok123', createdAt: '2025-01-01' }
// Get all hash fields with transform
const transformed = await redis.hgetall({
key: 'session:abc',
transform: (input) => ({ ...input, extra: true }),
});Key Scanning
// Find keys matching a pattern
const matchingKeys = await redis.keys({ key: 'user:*' });
// => ['user:1', 'user:2', ...]WARNING
keys() uses the Redis KEYS command, which scans the entire keyspace. Avoid using it in production on large databases -- prefer SCAN via execute() instead.
RedisJSON Operations
If your Redis server has the RedisJSON module installed, you can use the j* methods for native JSON document operations.
// Set a JSON document
await redis.jSet({ key: 'doc:1', path: '$', value: { name: 'Alice', scores: [10, 20] } });
// Get entire document (path defaults to '$')
const doc = await redis.jGet({ key: 'doc:1' });
// Get a nested path
const scores = await redis.jGet({ key: 'doc:1', path: '$.scores' });
// Delete a JSON path
await redis.jDelete({ key: 'doc:1', path: '$.scores' });
// Delete entire document (path defaults to '$')
await redis.jDelete({ key: 'doc:1' });
// Increment a number at a path
await redis.jNumberIncreaseBy({ key: 'doc:1', path: '$.counter', value: 5 });
// Append to a string at a path
await redis.jStringAppend({ key: 'doc:1', path: '$.name', value: ' Smith' });
// Push to an array at a path
await redis.jPush({ key: 'doc:1', path: '$.tags', value: 'new-tag' });
// Pop from an array at a path
const popped = await redis.jPop({ key: 'doc:1', path: '$.tags' });RedisJSON Method Signatures
| Method | Parameters | Returns | Redis Command |
|---|---|---|---|
jSet<T>() | { key: string; path: string; value: T } | Promise<string | null> | JSON.SET |
jGet<T>() | { key: string; path?: string } | Promise<T | null> | JSON.GET |
jDelete() | { key: string; path?: string } | Promise<number> | JSON.DEL |
jNumberIncreaseBy() | { key: string; path: string; value: number } | Promise<string | null> | JSON.NUMINCRBY |
jStringAppend() | { key: string; path: string; value: string } | Promise<number[] | null> | JSON.STRAPPEND |
jPush<T>() | { key: string; path: string; value: T } | Promise<number[] | null> | JSON.ARRAPPEND |
jPop<T>() | { key: string; path: string } | Promise<T | null> | JSON.ARRPOP |
jGet and jDelete default path to '$' (root) when omitted. All other j* methods require path explicitly.
Pub/Sub
The helper supports Redis Pub/Sub for real-time messaging with optional zlib compression.
// Subscribe to a topic
redis.subscribe({ topic: 'events' });
// Listen for messages on the underlying ioredis client
redis.getClient().on('message', (channel, message) => {
if (channel === 'events') {
console.log('Received:', JSON.parse(message));
}
});
// Publish to one or more topics
await redis.publish({
topics: ['events', 'audit-log'],
payload: { action: 'user.created', userId: 'u1' },
});
// Publish with zlib compression
await redis.publish({
topics: ['compressed-channel'],
payload: { large: 'dataset' },
useCompress: true,
});
// Unsubscribe from a topic
redis.unsubscribe({ topic: 'events' });IMPORTANT
When using Pub/Sub, the subscribing client enters subscriber mode and can only execute SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE, PUNSUBSCRIBE, PING, and QUIT commands. Use a separate RedisHelper instance for Pub/Sub if you also need to perform regular data operations.
Raw Command Execution
For commands not wrapped by the helper, use execute() to call any Redis command directly.
// Execute any Redis command
const result = await redis.execute<string>('SET', ['mykey', 'myvalue', 'EX', 60]);
// Command without parameters
const info = await redis.execute<string>('INFO');
// SCAN instead of KEYS for production use
const [cursor, keys] = await redis.execute<[string, string[]]>(
'SCAN', [0, 'MATCH', 'user:*', 'COUNT', 100],
);API Summary
| Method | Returns | Description |
|---|---|---|
| Connection | ||
connect() | Promise<boolean> | Manual connect (no-op if already connected/connecting/ready) |
disconnect() | Promise<boolean> | Graceful disconnect via QUIT (no-op if already ended/closed) |
ping() | Promise<string> | Health check, returns 'PONG' |
getClient() | Redis | Cluster | Access the underlying ioredis client |
| Key-Value | ||
set<T>(opts) | Promise<void> | Set a key with JSON-serialized value. Options: { key, value, options?: { log } } |
get<T>(opts) | Promise<T | null> | Get raw value with optional transform. Options: { key, transform? } |
getString(opts) | Promise<string | null> | Get raw string value. Options: { key } |
getObject(opts) | Promise<object | null> | Get value parsed as JSON. Options: { key } |
del(opts) | Promise<number> | Delete one or more keys. Options: { keys: string[] } |
| Multi-Key | ||
mset<T>(opts) / mSet<T>(opts) | Promise<void> | Set multiple key-value pairs. Options: { payload: Array<{ key, value }>, options?: { log } } |
mget<T>(opts) / mGet<T>(opts) | Promise<(T | null)[]> | Get multiple values with optional transform. Options: { keys, transform? } |
getStrings(opts) | Promise<(string | null)[]> | Get multiple raw string values. Options: { keys } |
getObjects(opts) | Promise<(object | null)[]> | Get multiple values parsed as JSON. Options: { keys } |
| Hashes | ||
hset<T>(opts) / hSet<T>(opts) | Promise<number> | Set hash fields. Options: { key, value: Record<string, unknown>, options?: { log } } |
hgetall(opts) / hGetAll(opts) | Promise<Record<string, string> | null> | Get all hash fields with optional transform. Options: { key, transform? } |
| Key Scanning | ||
keys(opts) | Promise<string[]> | Find keys matching a glob pattern. Options: { key } |
| RedisJSON | ||
jSet<T>(opts) | Promise<string | null> | Set a JSON document at path (JSON.SET) |
jGet<T>(opts) | Promise<T | null> | Get a JSON document or path (JSON.GET) |
jDelete(opts) | Promise<number> | Delete a JSON path (JSON.DEL) |
jNumberIncreaseBy(opts) | Promise<string | null> | Increment a number at path (JSON.NUMINCRBY) |
jStringAppend(opts) | Promise<number[] | null> | Append to a string at path (JSON.STRAPPEND) |
jPush<T>(opts) | Promise<number[] | null> | Push to an array at path (JSON.ARRAPPEND) |
jPop<T>(opts) | Promise<T | null> | Pop from an array at path (JSON.ARRPOP) |
| Pub/Sub | ||
subscribe(opts) | void | Subscribe to a topic. Options: { topic } |
publish<T>(opts) | Promise<void> | Publish to one or more topics with optional compression. Options: { topics, payload, useCompress? } |
unsubscribe(opts) | void | Unsubscribe from a topic. Options: { topic } |
| Raw | ||
execute<R>(command, parameters?) | Promise<R> | Execute any Redis command directly |
Troubleshooting
"[execute] Invalid client to execute | command: ..."
Cause: execute() was called when the ioredis client is null or undefined. This typically happens if the helper was not properly constructed or the connection was never established.
Fix: Ensure the helper is instantiated correctly and, if using autoConnect: false, call await redis.connect() before issuing commands.
"[subscribe] Failed to subscribe to topic: ..."
Cause: The ioredis subscribe() callback received an error. This can happen if the client lost its connection or the Redis server rejected the subscription.
Fix: Check that the Redis server is reachable and the client is in a valid state. Monitor the onError callback for connection issues.
"[unsubscribe] Failed to unsubscribe from topic: ..."
Cause: The ioredis unsubscribe() callback received an error, usually due to a broken connection.
Fix: Same as above -- verify connectivity and client state.
Connection Refused / Timeout
Symptoms: ECONNREFUSED, connection hangs, or onError fires immediately.
Checklist:
- Verify Redis is running and reachable at the configured
host:port - Check firewall rules and network access between your application and the Redis server
- If using
autoConnect: false, ensure you callawait redis.connect()before any operations - Verify
passwordis correct (Redis returns a generic error for auth failure) - For clusters, ensure all node addresses are reachable and the cluster is healthy (
redis-cli cluster info)
Pub/Sub Subscriber Mode Conflicts
Symptoms: ERR only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET allowed in this context
Cause: You called subscribe() on a client and then attempted a regular command (get, set, etc.) on the same client.
Fix: Use a separate connection for Pub/Sub:
const dataClient = new RedisHelper({ name: 'data', host, port, password });
const subClient = new RedisHelper({ name: 'sub', host, port, password });
// Use subClient only for subscribe/unsubscribe
subClient.subscribe({ topic: 'events' });
subClient.getClient().on('message', (channel, msg) => { /* ... */ });
// Use dataClient for everything else
await dataClient.set({ key: 'foo', value: 'bar' });RedisJSON Commands Return Errors
Symptoms: ERR unknown command 'JSON.SET'
Cause: The RedisJSON module is not installed on your Redis server.
Fix: Install Redis Stack or the RedisJSON module. See RedisJSON documentation.
See Also
Related Concepts:
- Services - Using Redis in services
Other Helpers:
- Helpers Index - All available helpers
- Queue Helper - BullMQ uses Redis as backend
References:
- DataSources - Database connections
External Resources:
- ioredis Documentation - Redis client library
- Redis Commands - Redis command reference
- RedisJSON - JSON module documentation
Best Practices:
- Performance Optimization - Caching strategies
- Security Guidelines - Redis security