Service API
Direct cache manipulation when decorators aren't enough.
Inject CacheService
import { Injectable } from '@nestjs/common';
import { CacheService } from '@nestjs-redisx/cache';
@Injectable()
export class UserService {
constructor(private readonly cache: CacheService) {}
}Or by token (useful for mocking in tests):
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_SERVICE, type ICacheService } from '@nestjs-redisx/cache';
@Injectable()
export class UserService {
constructor(@Inject(CACHE_SERVICE) private readonly cache: ICacheService) {}
}Basic Operations
Get
const user = await this.cache.get<User>('user:123');
// Returns: User | nullSet
// Simple set
await this.cache.set('user:123', user);
// With options
await this.cache.set('user:123', user, {
ttl: 300,
tags: ['users', 'user:123'],
strategy: 'l1-l2', // 'l1-only' | 'l2-only' | 'l1-l2' (default)
varyBy: { tenantId: 'acme' }, // Additional key variation (multi-tenant)
});varyBy in Service API vs decorators
In Service API, varyBy accepts resolved key-value pairs (Record<string, string>) — you provide the values directly. In @Cached decorator, varyBy accepts key names (string[]) — values are resolved automatically from contextProvider.
Get or Set (Cache-Aside)
Atomically get from cache or load and cache if missing. Includes anti-stampede protection.
const user = await this.cache.getOrSet<User>(
'user:123',
() => this.repository.findOne('123'),
{ ttl: 300, tags: ['users'] }
);Full options (inherits all set() options plus SWR and stampede control):
const user = await this.cache.getOrSet<User>(
'user:123',
() => this.repository.findOne('123'),
{
ttl: 300,
tags: ['users'],
strategy: 'l1-l2', // 'l1-only' | 'l2-only' | 'l1-l2'
varyBy: { tenantId: 'acme' }, // Additional key variation
swr: { enabled: true, staleTime: 60 }, // Stale-while-revalidate
skipStampede: true, // Bypass anti-stampede for this call
}
);Delete
// Single key
const deleted = await this.cache.del('user:123');
// Returns: boolean
// Multiple keys
const count = await this.cache.deleteMany(['user:123', 'user:456']);
// Returns: number (count of deleted keys)Has
const exists = await this.cache.has('user:123');
// Returns: booleanTTL
const ttl = await this.cache.ttl('user:123');
// Returns: number (seconds), -1 if no TTL, -2 if key doesn't existBatch Operations
Batch Get
const users = await this.cache.getMany<User>([
'user:1',
'user:2',
'user:3',
]);
// Returns: Array<User | null>Batch Set
await this.cache.setMany([
{ key: 'user:1', value: user1, ttl: 3600, tags: ['users'] },
{ key: 'user:2', value: user2, ttl: 3600, tags: ['users'] },
{ key: 'user:3', value: user3, ttl: 3600, tags: ['users'] },
]);INFO
setMany accepts key, value, ttl, and tags per entry. For strategy per entry, use individual set() calls.
Tag Operations
Get Keys by Tag
const keys = await this.cache.getKeysByTag('users');
// Returns: string[]Invalidate by Tag
// Single tag
const count = await this.cache.invalidate('users');
// Returns: number (count of invalidated keys)
// Multiple tags
const count = await this.cache.invalidateTags(['users', 'products']);
// Returns: number (total keys invalidated)Invalidate by Pattern
// Delete all keys matching pattern (uses Redis SCAN)
const count = await this.cache.invalidateByPattern('user:*');
// Returns: number (count of deleted keys)Wrap Function
Wrap any function with caching logic. Uses getOrSet internally (includes anti-stampede).
import { Injectable } from '@nestjs/common';
import { CacheService } from '@nestjs-redisx/cache';
import { UserRepository } from './types';
@Injectable()
export class UserService {
constructor(
private readonly cache: CacheService,
private readonly repository: UserRepository,
) {}
getById = this.cache.wrap(
async (id: string) => this.repository.findById(id),
{
key: (id: string) => `user:${id}`, // Key builder (required, function)
ttl: 3600, // TTL in seconds
tags: (id: string) => [`user:${id}`, 'users'], // Static array or function
}
);
}Clear All
// Use with caution in production
await this.cache.clear();Statistics
const stats = await this.cache.getStats();
/*
{
l1: {
hits: 15234,
misses: 1876,
size: 523,
},
l2: {
hits: 45123,
misses: 2341,
},
stampedePrevented: 142,
}
*/Error Handling
Read operations (get, has, ttl, getMany, getKeysByTag) are fail-open — errors return null/false/[].
Write operations (set, del, deleteMany, setMany, getOrSet, invalidate, clear) are fail-closed — errors throw.
| Scenario | Behavior |
|---|---|
Redis error on get / has / ttl | Fail-open. Returns null / false / -1. Error logged. |
Redis error on set / del / clear | Fail-closed. Throws CacheError. |
Key validation fails on get / has / ttl | Fail-open. Returns null / false / -1. Warning logged. |
Key validation fails on set / del / getOrSet | Fail-closed. Throws CacheKeyError. |
Loader throws in getOrSet | Error propagates. Cache is not updated. |
invalidate / invalidateTags fails | Fail-closed. Throws CacheError. |
invalidateByPattern fails | Fail-closed. Throws CacheError. |
Key validation rules: non-empty, no whitespace, only a-zA-Z0-9_-:., max 1024 chars (configurable via keys.maxLength).
Next Steps
- Tag Invalidation — Advanced tag patterns
- Anti-Stampede — Prevent cache stampede