Multiple Clients
Manage multiple Redis connections for different purposes.
Why Multiple Clients?
Common use cases for multiple Redis connections:
- Separation of concerns — Cache, sessions, queues on different instances
- Different databases — Use Redis DB 0 for cache, DB 1 for sessions
- Performance isolation — Heavy operations on dedicated connections
- Different servers — Primary for writes, replicas for reads
Configuration
Named Clients
typescript
import { Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-redisx/core';
@Module({
imports: [
RedisModule.forRoot({
clients: {
default: {
host: 'localhost',
port: 6379,
db: 0,
},
cache: {
host: 'cache-server',
port: 6379,
db: 1,
},
sessions: {
host: 'session-server',
port: 6379,
db: 2,
},
queue: {
host: 'queue-server',
port: 6379,
db: 3,
},
},
}),
],
})
export class AppModule {}Async Configuration
typescript
RedisModule.forRootAsync({
useFactory: (config: ConfigService) => ({
clients: {
default: {
host: config.get('REDIS_HOST'),
port: config.get('REDIS_PORT'),
},
cache: {
host: config.get('CACHE_REDIS_HOST'),
port: config.get('CACHE_REDIS_PORT'),
},
},
}),
inject: [ConfigService],
})Accessing Named Clients
Via RedisService
typescript
import { Injectable } from '@nestjs/common';
import { RedisService } from '@nestjs-redisx/core';
import { User, SessionData, Job, generateSessionId } from '../types';
@Injectable()
export class UserService {
constructor(private readonly redis: RedisService) {}
async cacheUser(user: User): Promise<void> {
const cache = await this.redis.getClient('cache');
await cache.set(`user:${user.id}`, JSON.stringify(user), { ex: 3600 });
}
async createSession(userId: string, data: SessionData): Promise<string> {
const sessions = await this.redis.getClient('sessions');
const sessionId = generateSessionId();
await sessions.set(
`session:${sessionId}`,
JSON.stringify({ userId, ...data }),
{ ex: 86400 },
);
return sessionId;
}
async queueJob(job: Job): Promise<void> {
const queue = await this.redis.getClient('queue');
await queue.rpush('jobs', JSON.stringify(job));
}
}Via @InjectRedis Decorator
typescript
import { Injectable } from '@nestjs/common';
import { InjectRedis, IRedisDriver } from '@nestjs-redisx/core';
@Injectable()
export class CacheService {
constructor(
@InjectRedis('cache')
private readonly cacheClient: IRedisDriver,
) {}
async get(key: string): Promise<string | null> {
return this.cacheClient.get(key);
}
async set(key: string, value: string, ttl: number): Promise<void> {
await this.cacheClient.set(key, value, { ex: ttl });
}
}Via ClientManager
typescript
import { Injectable, Inject } from '@nestjs/common';
import {
CLIENT_MANAGER,
RedisClientManager,
IConnectionStats,
} from '@nestjs-redisx/core';
@Injectable()
export class MultiClientService {
constructor(
@Inject(CLIENT_MANAGER)
private readonly clientManager: RedisClientManager,
) {}
getClientNames(): string[] {
return this.clientManager.getClientNames();
}
async healthCheckAll() {
return this.clientManager.healthCheck();
}
getStats(): IConnectionStats {
return this.clientManager.getStats();
}
async addClient(name: string, host: string, port: number): Promise<void> {
await this.clientManager.createClient(name, { host, port });
}
getClientInfo(name: string) {
return this.clientManager.getMetadata(name);
}
}Client Patterns
Read/Write Separation
typescript
RedisModule.forRoot({
clients: {
write: {
host: 'redis-primary',
port: 6379,
},
read: {
host: 'redis-replica',
port: 6379,
},
},
})typescript
import { Injectable } from '@nestjs/common';
import { RedisService } from '@nestjs-redisx/core';
@Injectable()
export class DataService {
constructor(private readonly redis: RedisService) {}
async read(key: string): Promise<string | null> {
const client = await this.redis.getClient('read');
return client.get(key);
}
async write(key: string, value: string): Promise<void> {
const client = await this.redis.getClient('write');
await client.set(key, value);
}
}Environment-Based Clients
typescript
RedisModule.forRootAsync({
useFactory: (config: ConfigService) => {
const clients: Record<string, ConnectionConfig> = {
default: {
host: config.get('REDIS_HOST'),
port: config.get('REDIS_PORT'),
},
};
// Add cache client only in production
if (config.get('NODE_ENV') === 'production') {
clients.cache = {
host: config.get('CACHE_HOST'),
port: config.get('CACHE_PORT'),
};
}
return { clients };
},
inject: [ConfigService],
})Different Connection Types
typescript
RedisModule.forRoot({
clients: {
// Single instance for development
dev: {
type: 'single',
host: 'localhost',
port: 6379,
},
// Cluster for production cache
cache: {
type: 'cluster',
nodes: [
{ host: 'cache-1', port: 6379 },
{ host: 'cache-2', port: 6379 },
{ host: 'cache-3', port: 6379 },
],
},
// Sentinel for HA sessions
sessions: {
type: 'sentinel',
sentinels: [
{ host: 'sentinel-1', port: 26379 },
{ host: 'sentinel-2', port: 26379 },
],
name: 'sessions-master',
},
},
})Client Lifecycle
Lazy Connection
Clients connect on first use, not at module initialization:
typescript
// Client not connected yet
const client = await this.redis.getClient('cache');
// Now connected (first use triggers connection)
await client.set('key', 'value');Connection Status
typescript
import { Injectable, Inject } from '@nestjs/common';
import { CLIENT_MANAGER, RedisClientManager } from '@nestjs-redisx/core';
@Injectable()
export class MonitorService {
constructor(
@Inject(CLIENT_MANAGER)
private readonly clientManager: RedisClientManager,
) {}
async checkClient(name: string): Promise<boolean> {
const client = await this.clientManager.getClient(name);
return client.isConnected();
}
async checkAll(): Promise<Record<string, boolean>> {
const names = this.clientManager.getClientNames();
const status: Record<string, boolean> = {};
for (const name of names) {
const client = await this.clientManager.getClient(name);
status[name] = client.isConnected();
}
return status;
}
}Graceful Shutdown
All clients are automatically closed on application shutdown:
typescript
// Automatic cleanup via onModuleDestroy
// No manual cleanup needed
// For manual cleanup:
await this.clientManager.closeClient('cache');
await this.clientManager.closeAll();Events
Listen to Client Events
typescript
import { Injectable, OnModuleInit, Inject, Logger } from '@nestjs/common';
import {
CLIENT_MANAGER,
RedisClientManager,
ManagerEvent,
} from '@nestjs-redisx/core';
@Injectable()
export class RedisMonitor implements OnModuleInit {
private readonly logger = new Logger(RedisMonitor.name);
constructor(
@Inject(CLIENT_MANAGER)
private readonly clientManager: RedisClientManager,
) {}
onModuleInit(): void {
this.clientManager.on(ManagerEvent.CONNECTED, (data) => {
this.logger.log(`Redis client '${data.name}' connected`);
});
this.clientManager.on(ManagerEvent.DISCONNECTED, (data) => {
this.logger.warn(`Redis client '${data.name}' disconnected`);
});
this.clientManager.on(ManagerEvent.RECONNECTING, (data) => {
this.logger.log(
`Redis client '${data.name}' reconnecting (attempt ${data.metadata?.attempt})`,
);
});
this.clientManager.on(ManagerEvent.ERROR, (data) => {
this.logger.error(
`Redis client '${data.name}' error: ${data.error?.message}`,
);
});
}
}Best Practices
Naming Conventions
typescript
// Good - descriptive names
clients: {
cache: { ... },
sessions: { ... },
queue: { ... },
analytics: { ... },
}
// Avoid - generic names
clients: {
redis1: { ... },
redis2: { ... },
}Client per Concern
typescript
// Good - separate concerns
const cache = await this.redis.getClient('cache');
const sessions = await this.redis.getClient('sessions');
// Avoid - mixing concerns on default client
const client = await this.redis.getClient();
await client.set('cache:user:1', ...);
await client.set('session:abc', ...);Error Handling
typescript
async safeGetClient(name: string): Promise<IRedisDriver | null> {
try {
return await this.redis.getClient(name);
} catch (error) {
this.logger.error(`Failed to get client ${name}:`, error);
return null;
}
}Next Steps
- Connection Types — Single, Cluster, Sentinel details
- Health Monitoring — Monitor client health
- Decorators — @InjectRedis usage