Skip to content

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

Released under the MIT License.