@develop-x/nest-redis
v1.0.3
Published
A NestJS Redis integration library that provides caching, pub/sub functionality, and decorators for method-level caching. This library simplifies Redis operations in NestJS applications with type-safe interfaces and easy-to-use decorators.
Keywords
Readme
@develop-x/nest-redis
A NestJS Redis integration library that provides caching, pub/sub functionality, and decorators for method-level caching. This library simplifies Redis operations in NestJS applications with type-safe interfaces and easy-to-use decorators.
Features
- Redis Caching: Built-in caching service with JSON serialization
- Decorator Support: Method-level caching with flexible key generation
- Pub/Sub Messaging: Redis publish/subscribe functionality
- Type Safety: Full TypeScript support with generic types
- Flexible Key Generation: Multiple strategies for cache key generation
- TTL Support: Time-to-live configuration for cached data
- Global Module: Available across your entire application
Installation
npm install @develop-x/nest-redisDependencies
This library requires the following peer dependencies:
@nestjs/common ^11.0.1@nestjs/core ^11.0.1
The following dependencies are included:
ioredis ^5.6.1- Redis client
Setup
1. Import the Module
import { Module } from '@nestjs/common';
import { RedisModule } from '@develop-x/nest-redis';
@Module({
imports: [
RedisModule.forRootAsync({
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
host: config.get<string>('REDIS_HOST'),
port: config.get<number>('REDIS_PORT'),
password: config.get<string>('REDIS_PASS'),
})
}),
],
})
export class AppModule {}
2. Inject Services in Your Classes
import { Injectable } from '@nestjs/common';
import { RedisService, RedisCacheService } from '@develop-x/nest-redis';
@Injectable()
export class UserService {
constructor(
private readonly redisService: RedisService,
private readonly redisCacheService: RedisCacheService,
) {}
}Usage
Caching with Decorator
Basic Usage
import { RedisCacheable } from '@develop-x/nest-redis';
@Injectable()
export class UserService {
constructor(private readonly redisCacheService: RedisCacheService) {}
// Cache with static key
@RedisCacheable({
key: 'all-users',
ttl: 300, // 5 minutes
})
async getAllUsers() {
// This method result will be cached for 5 minutes
return await this.fetchUsersFromDatabase();
}
// Cache with dynamic key from method arguments
@RedisCacheable({
keys: ['userId'], // Extract userId from the first argument object
ttl: 600, // 10 minutes
})
async getUserProfile(params: { userId: string }) {
// Cached per userId
return await this.fetchUserFromDatabase(params.userId);
}
// Cache with custom key function
@RedisCacheable({
keyFn: (userId: string, includeMetadata: boolean) =>
`user:${userId}:meta:${includeMetadata}`,
ttl: 1800, // 30 minutes
})
async getUserData(userId: string, includeMetadata: boolean = false) {
// Custom cache key generation
return await this.fetchUserData(userId, includeMetadata);
}
// Cache empty results
@RedisCacheable({
key: 'empty-example',
ttl: 60,
cacheEmpty: true, // Cache null/undefined results
})
async getMaybeEmptyData() {
// Even null results will be cached
return await this.fetchDataThatMightBeEmpty();
}
}Manual Caching with Service
@Injectable()
export class ProductService {
constructor(private readonly redisCacheService: RedisCacheService) {}
async getProduct(productId: string) {
const cacheKey = `product:${productId}`;
// Try to get from cache first
const cached = await this.redisCacheService.get<Product>(cacheKey);
if (cached) {
return cached;
}
// Fetch from database and cache
const product = await this.fetchProductFromDB(productId);
await this.redisCacheService.set(cacheKey, product, 3600); // 1 hour TTL
return product;
}
async updateProduct(productId: string, data: Partial<Product>) {
const product = await this.updateProductInDB(productId, data);
// Update cache
const cacheKey = `product:${productId}`;
await this.redisCacheService.set(cacheKey, product, 3600);
return product;
}
async deleteProduct(productId: string) {
await this.deleteProductFromDB(productId);
// Remove from cache
const cacheKey = `product:${productId}`;
await this.redisCacheService.del(cacheKey);
}
async getOrCreateProduct(productId: string) {
const cacheKey = `product:${productId}`;
return await this.redisCacheService.getOrSet(
cacheKey,
3600, // TTL
async () => {
// This function only runs if cache miss
return await this.fetchOrCreateProduct(productId);
},
false // Don't cache empty results
);
}
}Pub/Sub Messaging
@Injectable()
export class NotificationService {
constructor(private readonly redisService: RedisService) {}
async publishNotification(userId: string, message: any) {
const channel = `user:${userId}:notifications`;
const serializedMessage = JSON.stringify(message);
const subscribersCount = await this.redisService.publish(channel, serializedMessage);
console.log(`Message sent to ${subscribersCount} subscribers`);
}
async subscribeToUserNotifications(userId: string) {
const channel = `user:${userId}:notifications`;
await this.redisService.subscribe(channel, (message) => {
const notification = JSON.parse(message);
console.log('Received notification:', notification);
// Process notification
this.handleNotification(notification);
});
}
}
@Injectable()
export class EventService {
constructor(private readonly redisService: RedisService) {}
async publishEvent(eventType: string, data: any) {
const channel = `events:${eventType}`;
await this.redisService.publish(channel, JSON.stringify({
type: eventType,
data,
timestamp: new Date().toISOString(),
}));
}
async subscribeToEvents(eventType: string, handler: (data: any) => void) {
const channel = `events:${eventType}`;
await this.redisService.subscribe(channel, (message) => {
const event = JSON.parse(message);
handler(event.data);
});
}
}Direct Redis Client Access
@Injectable()
export class CustomRedisService {
constructor(private readonly redisService: RedisService) {}
async performCustomRedisOperation() {
const client = this.redisService.getClient();
// Use any Redis command directly
await client.hset('user:123', 'name', 'John Doe');
await client.hset('user:123', 'email', '[email protected]');
const userData = await client.hgetall('user:123');
return userData;
}
async useRedisStreams() {
const client = this.redisService.getClient();
// Add to stream
await client.xadd('events', '*', 'type', 'user_login', 'userId', '123');
// Read from stream
const messages = await client.xread('STREAMS', 'events', '0');
return messages;
}
async useRedisSets() {
const client = this.redisService.getClient();
// Set operations
await client.sadd('active_users', 'user1', 'user2', 'user3');
const activeUsers = await client.smembers('active_users');
return activeUsers;
}
}Use Cases
1. API Response Caching
Cache expensive database queries or external API calls:
@RedisCacheable({
keys: ['endpoint', 'params'],
ttl: 300, // 5 minutes
})
async fetchExternalApiData(request: { endpoint: string, params: any }) {
// Expensive external API call
return await this.httpService.get(request.endpoint, { params: request.params });
}2. User Session Management
Store and retrieve user sessions:
async createUserSession(userId: string, sessionData: any) {
const sessionKey = `session:${userId}`;
await this.redisCacheService.set(sessionKey, sessionData, 3600); // 1 hour
}
async getUserSession(userId: string) {
const sessionKey = `session:${userId}`;
return await this.redisCacheService.get(sessionKey);
}3. Real-time Notifications
Implement real-time messaging:
// Publisher
async notifyUsers(userIds: string[], message: any) {
for (const userId of userIds) {
await this.redisService.publish(`user:${userId}:updates`, JSON.stringify(message));
}
}
// Subscriber
async startListeningForUpdates(userId: string) {
await this.redisService.subscribe(`user:${userId}:updates`, (message) => {
const update = JSON.parse(message);
this.webSocketGateway.sendToUser(userId, update);
});
}4. Computed Data Caching
Cache expensive calculations:
@RedisCacheable({
keyFn: (reportType: string, dateRange: string) => `report:${reportType}:${dateRange}`,
ttl: 7200, // 2 hours
})
async generateReport(reportType: string, dateRange: string) {
// Expensive report generation
return await this.computeComplexReport(reportType, dateRange);
}5. Rate Limiting Data
Store rate limiting counters:
async incrementApiUsage(userId: string): Promise<number> {
const key = `api_usage:${userId}:${new Date().getHours()}`;
const client = this.redisService.getClient();
const current = await client.incr(key);
await client.expire(key, 3600); // Expire in 1 hour
return current;
}6. Event Broadcasting
Broadcast application events:
async broadcastUserAction(action: string, userId: string, data: any) {
await this.redisService.publish('user_actions', JSON.stringify({
action,
userId,
data,
timestamp: Date.now(),
}));
}Configuration Options
RedisCacheable Decorator Options
| Option | Type | Description | Default |
|--------|------|-------------|---------|
| key | string | Static cache key | - |
| keys | string[] | Extract values from method arguments to build key | - |
| keyFn | function | Custom function to generate cache key | - |
| ttl | number | Time-to-live in seconds | Required |
| cacheEmpty | boolean | Whether to cache null/undefined results | false |
Redis Module Options
The module accepts all standard ioredis configuration options:
| Option | Type | Description | Default |
|--------|------|-------------|---------|
| host | string | Redis server hostname | localhost |
| port | number | Redis server port | 6379 |
| password | string | Redis authentication password | - |
| db | number | Redis database number | 0 |
| connectTimeout | number | Connection timeout in milliseconds | 10000 |
| retryDelayOnFailover | number | Retry delay on failover | 100 |
API Reference
RedisCacheService
Methods
get<T>(key: string): Promise<T | null>- Get cached valueset<T>(key: string, value: T, ttlSeconds?: number): Promise<void>- Set cached valuedel(key: string): Promise<void>- Delete cached valuegetOrSet<T>(key: string, ttl: number, fn: () => Promise<T>, cacheEmpty?: boolean): Promise<T>- Get from cache or execute function and cache result
RedisService
Methods
getClient(): Redis- Get direct access to Redis clientpublish(channel: string, message: string): Promise<number>- Publish message to channelsubscribe(channel: string, handler: (message: string) => void): Promise<void>- Subscribe to channel
Best Practices
- Choose appropriate TTL values: Balance between data freshness and performance
- Use meaningful cache keys: Include version numbers or timestamps when needed
- Handle cache failures gracefully: Always have fallback mechanisms
- Monitor Redis memory usage: Implement cache eviction policies
- Use pub/sub for real-time features: Perfect for notifications and live updates
- Serialize data consistently: The library handles JSON serialization automatically
- Consider cache warming: Pre-populate frequently accessed data
- Use cache invalidation: Remove stale data when underlying data changes
Error Handling
The library will throw errors in the following cases:
- Redis connection failures
- Invalid decorator configuration (missing key options)
- Service injection failures
Always wrap Redis operations in try-catch blocks for production applications.
License
This library is part of the @develop-x namespace and follows the project's licensing terms.
