Skip to content

Decorators

Inject Redis clients directly into your services.

@InjectRedis

Inject Redis driver instance by name.

Basic Usage

typescript
import { Injectable } from '@nestjs/common';
import { InjectRedis, IRedisDriver } from '@nestjs-redisx/core';

@Injectable()
export class CacheService {
  constructor(
    @InjectRedis()
    private readonly redis: IRedisDriver,
  ) {}

  async get(key: string): Promise<string | null> {
    return this.redis.get(key);
  }

  async set(key: string, value: string, ttl?: number): Promise<void> {
    if (ttl) {
      await this.redis.set(key, value, { ex: ttl });
    } else {
      await this.redis.set(key, value);
    }
  }
}

Named Client

typescript
import { Injectable } from '@nestjs/common';
import { InjectRedis, IRedisDriver } from '@nestjs-redisx/core';
import { SessionData, generateSessionId } from '../types';

@Injectable()
export class SessionService {
  constructor(
    @InjectRedis('sessions')
    private readonly sessionStore: IRedisDriver,
  ) {}

  async createSession(userId: string, data: SessionData): Promise<string> {
    const sessionId = generateSessionId();
    await this.sessionStore.set(
      `session:${sessionId}`,
      JSON.stringify({ userId, ...data }),
      { ex: 86400 },
    );
    return sessionId;
  }

  async getSession(sessionId: string): Promise<SessionData | null> {
    const data = await this.sessionStore.get(`session:${sessionId}`);
    return data ? JSON.parse(data) : null;
  }
}

Multiple Injections

typescript
import { Injectable } from '@nestjs/common';
import { InjectRedis, IRedisDriver } from '@nestjs-redisx/core';
import { Job, Session } from '../types';

@Injectable()
export class DataService {
  constructor(
    @InjectRedis('cache')
    private readonly cache: IRedisDriver,

    @InjectRedis('queue')
    private readonly queue: IRedisDriver,

    @InjectRedis('sessions')
    private readonly sessions: IRedisDriver,
  ) {}

  async cacheData(key: string, value: string): Promise<void> {
    await this.cache.set(key, value, { ex: 3600 });
  }

  async enqueueJob(job: Job): Promise<void> {
    await this.queue.rpush('jobs', JSON.stringify(job));
  }

  async getSession(id: string): Promise<Session | null> {
    const data = await this.sessions.get(`session:${id}`);
    return data ? JSON.parse(data) : null;
  }
}

@InjectRedis vs RedisService

When to Use @InjectRedis

  • Direct driver access needed
  • Working with specific named clients
  • Lower-level Redis operations
  • Custom wrapper services
typescript
import { Injectable } from '@nestjs/common';
import { InjectRedis, IRedisDriver } from '@nestjs-redisx/core';

@Injectable()
export class LowLevelCacheService {
  constructor(
    @InjectRedis('cache')
    private readonly redis: IRedisDriver,
  ) {}

  // Direct driver access
  async pipeline(): Promise<void> {
    const pipe = this.redis.pipeline();
    pipe.set('key1', 'value1');
    pipe.set('key2', 'value2');
    await pipe.exec();
  }
}

When to Use RedisService

  • Simple Redis operations
  • Default client is sufficient
  • Higher-level API preferred
  • Dynamic client selection
typescript
import { Injectable } from '@nestjs/common';
import { RedisService } from '@nestjs-redisx/core';

@Injectable()
export class HighLevelCacheService {
  constructor(private readonly redis: RedisService) {}

  // Higher-level API
  async cache(key: string, value: string): Promise<void> {
    await this.redis.set(key, value, { ex: 3600 });
  }

  // Dynamic client selection
  async getFromClient(clientName: string, key: string): Promise<string | null> {
    const client = await this.redis.getClient(clientName);
    return client.get(key);
  }
}

Injection Tokens

CLIENT_MANAGER

Inject the client manager directly:

typescript
import { Injectable, Inject } from '@nestjs/common';
import { CLIENT_MANAGER, RedisClientManager, IHealthStatus } from '@nestjs-redisx/core';

@Injectable()
export class AdminService {
  constructor(
    @Inject(CLIENT_MANAGER)
    private readonly clientManager: RedisClientManager,
  ) {}

  getClientNames(): string[] {
    return this.clientManager.getClientNames();
  }

  async healthCheck(): Promise<IHealthStatus[]> {
    return this.clientManager.healthCheck() as Promise<IHealthStatus[]>;
  }
}

Custom Tokens

typescript
import { Injectable, Inject } from '@nestjs/common';
import { getClientToken, IRedisDriver } from '@nestjs-redisx/core';

// Get token for specific client
const CACHE_CLIENT = getClientToken('cache');

@Injectable()
export class CacheService {
  constructor(
    @Inject(CACHE_CLIENT)
    private readonly cache: IRedisDriver,
  ) {}
}

Type Safety

IRedisDriver Interface

The IRedisDriver interface provides full type safety:

typescript
interface IRedisDriver {
  // Connection
  connect(): Promise<void>;
  disconnect(): Promise<void>;
  isConnected(): boolean;
  ping(message?: string): Promise<string>;

  // String commands
  get(key: string): Promise<string | null>;
  set(key: string, value: string, options?: ISetOptions): Promise<'OK' | null>;
  mget(...keys: string[]): Promise<Array<string | null>>;
  mset(data: Record<string, string>): Promise<'OK'>;

  // Hash commands
  hget(key: string, field: string): Promise<string | null>;
  hset(key: string, field: string, value: string): Promise<number>;
  hgetall(key: string): Promise<Record<string, string>>;

  // List commands
  lpush(key: string, ...values: string[]): Promise<number>;
  rpush(key: string, ...values: string[]): Promise<number>;
  lpop(key: string): Promise<string | null>;
  rpop(key: string): Promise<string | null>;

  // Set commands
  sadd(key: string, ...members: string[]): Promise<number>;
  srem(key: string, ...members: string[]): Promise<number>;
  smembers(key: string): Promise<string[]>;

  // Sorted set commands
  zadd(key: string, ...args: Array<number | string>): Promise<number>;
  zrange(key: string, start: number, stop: number): Promise<string[]>;

  // Transaction commands
  pipeline(): IPipeline;
  multi(): IMulti;

  // Lua scripts
  eval(script: string, keys: string[], args: Array<string | number>): Promise<unknown>;

  // ... and more
}

ISetOptions

typescript
interface ISetOptions {
  ex?: number;      // Expiration in seconds
  px?: number;      // Expiration in milliseconds
  exat?: number;    // Expiration Unix timestamp (seconds)
  pxat?: number;    // Expiration Unix timestamp (milliseconds)
  nx?: boolean;     // Only set if not exists
  xx?: boolean;     // Only set if exists
  get?: boolean;    // Return previous value
  keepttl?: boolean; // Keep existing TTL
}

Testing with Decorators

Mock Injection

typescript
import { Test } from '@nestjs/testing';
import { getClientToken } from '@nestjs-redisx/core';
import { describe, it, expect, beforeEach, vi, type MockedObject } from 'vitest';

describe('CacheService', () => {
  let service: CacheService;
  let mockRedis: MockedObject<IRedisDriver>;

  beforeEach(async () => {
    mockRedis = {
      get: vi.fn(),
      set: vi.fn(),
      del: vi.fn(),
      // ... other methods
    } as unknown as MockedObject<IRedisDriver>;

    const module = await Test.createTestingModule({
      providers: [
        CacheService,
        {
          provide: getClientToken('cache'),
          useValue: mockRedis,
        },
      ],
    }).compile();

    service = module.get(CacheService);
  });

  it('should cache value', async () => {
    mockRedis.set.mockResolvedValue('OK');

    await service.set('key', 'value', 3600);

    expect(mockRedis.set).toHaveBeenCalledWith('key', 'value', { ex: 3600 });
  });
});

Integration Testing

typescript
import { Test } from '@nestjs/testing';
import { RedisModule } from '@nestjs-redisx/core';

describe('CacheService (integration)', () => {
  let service: CacheService;

  beforeAll(async () => {
    const module = await Test.createTestingModule({
      imports: [
        RedisModule.forRoot({
          clients: {
            cache: {
              host: 'localhost',
              port: 6379,
            },
          },
        }),
      ],
      providers: [CacheService],
    }).compile();

    service = module.get(CacheService);
  });

  it('should cache and retrieve value', async () => {
    await service.set('test-key', 'test-value', 60);
    const value = await service.get('test-key');
    expect(value).toBe('test-value');
  });
});

Common Patterns

Repository Pattern

typescript
import { Injectable } from '@nestjs/common';
import { InjectRedis, IRedisDriver } from '@nestjs-redisx/core';
import { User } from '../types';

@Injectable()
export class UserRepository {
  constructor(
    @InjectRedis('users')
    private readonly redis: IRedisDriver,
  ) {}

  async findById(id: string): Promise<User | null> {
    const data = await this.redis.hgetall(`user:${id}`);
    if (!Object.keys(data).length) return null;
    return this.mapToUser(data);
  }

  async save(user: User): Promise<void> {
    await this.redis.hmset(`user:${user.id}`, this.mapFromUser(user));
  }

  async delete(id: string): Promise<void> {
    await this.redis.del(`user:${id}`);
  }

  private mapToUser(data: Record<string, string>): User {
    return {
      id: data.id,
      name: data.name,
      email: data.email,
      createdAt: new Date(data.createdAt),
    };
  }

  private mapFromUser(user: User): Record<string, string> {
    return {
      id: user.id,
      name: user.name,
      email: user.email,
      createdAt: user.createdAt.toISOString(),
    };
  }
}

Queue Service

typescript
import { Injectable } from '@nestjs/common';
import { InjectRedis, IRedisDriver } from '@nestjs-redisx/core';

@Injectable()
export class QueueService {
  constructor(
    @InjectRedis('queue')
    private readonly redis: IRedisDriver,
  ) {}

  async enqueue<T>(queue: string, item: T): Promise<void> {
    await this.redis.rpush(queue, JSON.stringify(item));
  }

  async dequeue<T>(queue: string): Promise<T | null> {
    const data = await this.redis.lpop(queue);
    return data ? JSON.parse(data) : null;
  }

  async length(queue: string): Promise<number> {
    return this.redis.llen(queue);
  }
}

Accessing Native Client via Decorator

When you need driver-specific features, inject the native client directly:

typescript
import { InjectRedis } from '@nestjs-redisx/core';
import Redis from 'ioredis';

@Injectable()
export class CustomService {
  constructor(@InjectRedis() private redis: Redis) {}

  async customOp(): Promise<void> {
    // ioredis-specific: define custom commands
    this.redis.defineCommand('mycommand', {
      numberOfKeys: 1,
      lua: 'return redis.call("get", KEYS[1])',
    });
  }
}
typescript
import { InjectRedis } from '@nestjs-redisx/core';
import { RedisClientType } from 'redis';

@Injectable()
export class CustomService {
  constructor(@InjectRedis() private redis: RedisClientType) {}

  async customOp(): Promise<void> {
    // node-redis-specific: send raw command
    await this.redis.sendCommand(['CLIENT', 'INFO']);
  }
}

Next Steps

Released under the MIT License.