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
- Driver Abstraction — ioredis vs node-redis
- RedisService — High-level API
- Multiple Clients — Named clients