@nam088/nestjs-redis
v1.1.3
Published
A NestJS wrapper for ioredis with enhanced features including multiple connections, cluster support, and utility methods
Maintainers
Readme
@nam088/nestjs-redis
A NestJS wrapper for ioredis with advanced features
Features
- Synchronous and asynchronous configuration support
- Multiple Redis connections support
- Redis Cluster mode support
- Automatic reconnection and lifecycle management
- Utility methods (JSON support, TTL, etc.)
- Full TypeScript support
@InjectRedis()decorator for easy client injection- Built-in health check endpoint
Installation
npm install @nam088/nestjs-redis ioredis
# or
yarn add @nam088/nestjs-redis ioredis
# or
pnpm add @nam088/nestjs-redis ioredisUsage
1. Basic Configuration (Synchronous)
import { Module } from '@nestjs/common';
import { RedisModule } from '@nam088/nestjs-redis';
@Module({
imports: [
RedisModule.forRoot({
host: 'localhost',
port: 6379,
password: 'your-password', // optional
db: 0, // optional, default is 0
}),
],
})
export class AppModule {}2. Asynchronous Configuration with ConfigService
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { RedisModule } from '@nam088/nestjs-redis';
@Module({
imports: [
ConfigModule.forRoot(),
RedisModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
host: configService.get('REDIS_HOST'),
port: configService.get('REDIS_PORT'),
password: configService.get('REDIS_PASSWORD'),
db: configService.get('REDIS_DB'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}3. Multiple Redis Connections
import { Module } from '@nestjs/common';
import { RedisModule } from '@nam088/nestjs-redis';
@Module({
imports: [
// Connection 1: Default
RedisModule.forRoot({
name: 'default',
host: 'localhost',
port: 6379,
}),
// Connection 2: Cache
RedisModule.forRoot({
name: 'cache',
host: 'localhost',
port: 6380,
}),
// Connection 3: Session
RedisModule.forRoot({
name: 'session',
host: 'localhost',
port: 6381,
}),
],
})
export class AppModule {}4. Redis Cluster Mode
import { Module } from '@nestjs/common';
import { RedisModule } from '@nam088/nestjs-redis';
@Module({
imports: [
RedisModule.forRoot({
isCluster: true,
clusterNodes: [
{ host: 'localhost', port: 7000 },
{ host: 'localhost', port: 7001 },
{ host: 'localhost', port: 7002 },
],
}),
],
})
export class AppModule {}Using in Services
Method 1: Using RedisService
import { Injectable } from '@nestjs/common';
import { RedisService } from '@nam088/nestjs-redis';
@Injectable()
export class UserService {
constructor(private readonly redisService: RedisService) {}
async cacheUser(userId: string, userData: any) {
// Set with TTL of 1 hour
await this.redisService.setWithTTL(
`user:${userId}`,
userData,
3600
);
}
async getUser(userId: string) {
// Get and automatically parse JSON
return await this.redisService.getJSON(`user:${userId}`);
}
async deleteUser(userId: string) {
await this.redisService.delete(`user:${userId}`);
}
// Using named connection
async cacheInSpecificRedis() {
await this.redisService.setJSON(
'key',
{ data: 'value' },
'cache' // connection name
);
}
}Method 2: Direct Redis Client Injection
import { Injectable } from '@nestjs/common';
import { InjectRedis } from '@nam088/nestjs-redis';
import { Redis } from 'ioredis';
@Injectable()
export class ProductService {
constructor(
@InjectRedis() private readonly redis: Redis,
@InjectRedis('cache') private readonly cacheRedis: Redis,
) {}
async setProduct(productId: string, data: any) {
// Use ioredis methods directly
await this.redis.set(
`product:${productId}`,
JSON.stringify(data),
'EX',
3600
);
}
async getProduct(productId: string) {
const data = await this.redis.get(`product:${productId}`);
return data ? JSON.parse(data) : null;
}
async cacheProductList(products: any[]) {
// Using named connection
await this.cacheRedis.set(
'products:list',
JSON.stringify(products),
'EX',
300
);
}
}Method 3: Using Dynamic Client Context (Recommended for Multi-Tenant)
The withClient() method allows you to create a context-aware wrapper that automatically uses a specific Redis client for all subsequent operations. This is cleaner and safer than passing clientName to direct methods.
import { Injectable } from '@nestjs/common';
import { RedisService } from '@nam088/nestjs-redis';
@Injectable()
export class AnalyticsService {
constructor(private readonly redisService: RedisService) {}
async logEvent(userId: string, event: string) {
// Switch completely to 'analytics' client context
const analytics = this.redisService.withClient('analytics');
// All calls here automatically use 'analytics' connection
await analytics.listPushRight('events', JSON.stringify({ userId, event }));
await analytics.increment(`stats:${event}`);
// You can even chain contexts (though rarely needed)
// const reports = analytics.withClient('reports');
}
}Health Check
The module includes a built-in health check endpoint to monitor Redis connection status.
Setup Health Check Module
import { Module } from '@nestjs/common';
import { RedisHealthModule } from '@nam088/nestjs-redis';
@Module({
imports: [
RedisHealthModule,
],
})
export class AppModule {}Health Check Endpoints
The health check controller provides the following endpoints:
GET /health/redis- Check all Redis connectionsGET /health/redis/:name- Check specific Redis connection by name
Response Format
// Success response
{
"status": "up",
"connections": {
"default": "connected",
"cache": "connected"
}
}
// Failure response
{
"status": "down",
"connections": {
"default": "connected",
"cache": "disconnected"
},
"error": "Some Redis connections are down"
}Custom Health Check
You can also use the RedisHealthIndicator directly in your own health checks:
import { Injectable } from '@nestjs/common';
import { HealthCheckService, HealthCheck } from '@nestjs/terminus';
import { RedisHealthIndicator } from '@nam088/nestjs-redis';
@Injectable()
export class CustomHealthService {
constructor(
private health: HealthCheckService,
private redisHealth: RedisHealthIndicator,
) {}
@HealthCheck()
async check() {
return this.health.check([
// With default 5 second timeout
() => this.redisHealth.isHealthy('redis'),
// With custom timeout
() => this.redisHealth.isHealthy('redis', 'default', 3000),
// Check all connections with timeout
() => this.redisHealth.checkAllConnections('all-redis', 5000),
]);
}
}API Reference
RedisService Methods
Basic Methods
// Get a string value
get(key: string, clientName?: string): Promise<string | null>
// Set a string value
set(key: string, value: string, clientName?: string): Promise<'OK'>
// Get Redis client
getClient(name?: string): Redis | Cluster
// Get all clients
getClients(): Map<string, Redis | Cluster>
// Get dynamic context (fluent API)
withClient(clientName: string): IRedisServiceJSON Methods
// Get and automatically parse JSON (throws error if parse fails)
getJSON<T = unknown>(key: string, clientName?: string): Promise<T | null>
// Get JSON with custom validation
getJSONValidated<T>(key: string, validator: (value: unknown) => T, clientName?: string): Promise<T | null>
// Set JSON value
setJSON(key: string, value: unknown, clientName?: string): Promise<'OK'>Note:
getJSON()will throw an error if the stored value is not valid JSON. Useget()for raw string values.
Utility Methods
// Set with TTL
setWithTTL(key: string, value: unknown, ttlSeconds: number, clientName?: string): Promise<'OK'>
// Delete one or multiple keys
delete(keys: string | string[], clientName?: string): Promise<number>
// Check if key exists
exists(key: string, clientName?: string): Promise<boolean>
// Get TTL of key
getTTL(key: string, clientName?: string): Promise<number>
// Increment value
increment(key: string, amount?: number, clientName?: string): Promise<number>
// Decrement value
decrement(key: string, amount?: number, clientName?: string): Promise<number>
// Flush database
flushDB(clientName?: string): Promise<'OK'>
// Scan keys matching pattern (production-safe)
scanKeysToArray(pattern: string, count?: number, clientName?: string): Promise<string[]>Deprecated Methods
// @deprecated - Use scanKeysToArray() instead
keys(pattern: string, clientName?: string): Promise<string[]>Warning:
keys()is deprecated because it's O(N) and can block Redis on large databases. UsescanKeysToArray()for production-safe key scanning.
Utility Methods Examples
@Injectable()
export class CacheService {
constructor(private readonly redisService: RedisService) {}
// Cache with TTL
async cacheData(key: string, data: any, ttl: number = 3600) {
await this.redisService.setWithTTL(key, data, ttl);
}
// Get cached data
async getCachedData<T>(key: string): Promise<T | null> {
return await this.redisService.getJSON<T>(key);
}
// Increment counter
async incrementPageView(pageId: string) {
return await this.redisService.increment(`page:${pageId}:views`);
}
// Check if exists
async hasCache(key: string): Promise<boolean> {
return await this.redisService.exists(key);
}
// Get TTL
async getCacheTTL(key: string): Promise<number> {
return await this.redisService.getTTL(key);
}
// Delete multiple keys (production-safe)
async clearUserCache(userId: string) {
const keys = await this.redisService.scanKeysToArray(`user:${userId}:*`);
if (keys.length > 0) {
await this.redisService.delete(keys);
}
}
}Real-World Examples
Session Management
@Injectable()
export class SessionService {
constructor(
@InjectRedis('session') private readonly sessionRedis: Redis,
) {}
async createSession(userId: string, sessionData: any): Promise<string> {
const sessionId = randomUUID();
await this.sessionRedis.setex(
`session:${sessionId}`,
3600, // 1 hour
JSON.stringify({ userId, ...sessionData })
);
return sessionId;
}
async getSession(sessionId: string): Promise<any | null> {
const data = await this.sessionRedis.get(`session:${sessionId}`);
return data ? JSON.parse(data) : null;
}
async destroySession(sessionId: string): Promise<void> {
await this.sessionRedis.del(`session:${sessionId}`);
}
}Rate Limiting
@Injectable()
export class RateLimitService {
constructor(private readonly redisService: RedisService) {}
async checkRateLimit(
userId: string,
limit: number = 100,
windowSeconds: number = 60
): Promise<boolean> {
const key = `rate_limit:${userId}`;
const current = await this.redisService.increment(key);
if (current === 1) {
// First request, set TTL
const client = this.redisService.getClient();
await client.expire(key, windowSeconds);
}
return current <= limit;
}
}Caching with Decorator Pattern
@Injectable()
export class PostService {
constructor(
private readonly redisService: RedisService,
private readonly postRepository: PostRepository,
) {}
async getPost(postId: string) {
// Try cache first
const cached = await this.redisService.getJSON(`post:${postId}`);
if (cached) {
return cached;
}
// Cache miss, get from database
const post = await this.postRepository.findById(postId);
if (post) {
// Cache for 10 minutes
await this.redisService.setWithTTL(`post:${postId}`, post, 600);
}
return post;
}
async updatePost(postId: string, data: any) {
const updatedPost = await this.postRepository.update(postId, data);
// Invalidate cache
await this.redisService.delete(`post:${postId}`);
return updatedPost;
}
}Configuration Options
All ioredis options are supported. Some common options:
interface RedisModuleOptions {
// Basic
host?: string; // default: 'localhost'
port?: number; // default: 6379
password?: string;
db?: number; // default: 0
// Connection
name?: string; // connection name, default: 'default'
connectTimeout?: number; // default: 10000ms
keepAlive?: number; // default: 30000ms
// Cluster
isCluster?: boolean; // enable cluster mode
clusterNodes?: Array<{ host: string; port: number }>;
// Retry
retryStrategy?: (times: number) => number | null;
maxRetriesPerRequest?: number;
// Other ioredis options
enableReadyCheck?: boolean;
enableOfflineQueue?: boolean;
lazyConnect?: boolean;
// ... and many other ioredis options
}Testing
The module includes comprehensive test coverage:
# Run tests
npm test
# Run tests with coverage
npm run test:cov
# Run linter
npm run lintLicense
MIT
Contributing
Contributions, issues, and feature requests are welcome!
Author
nam088
Made with care by nam088
