A configurable NestJS module to integrate Redis, built on top of ioredis. This module provides a RedisService with a comprehensive set of methods to interact with your Redis server.
- Easy integration with NestJS applications.
- Built on the robust and performant
ioredislibrary. - Configurable using static
registeror dynamicregisterAsyncmethods. - Global module support.
- Built-in caching interceptor to cache method responses with customizable keys and TTLs.
- Ai Autotuning of TTL per endpoint (coming very soon).
- A rich
RedisServicethat covers most Redis commands for various data structures.
You can import RedisModule into your application's root module (app.module.ts).
For simple setups, you can configure the module statically using register.
// app.module.ts
import { Module } from '@nestjs/common';
import { RedisModule } from './redis/redis.module'; // Adjust path as needed
@Module({
imports: [
RedisModule.register({
host: 'localhost',
port: 6379,
// You can add any other ioredis options here
}),
],
})
export class AppModule {}For more complex setups, like when you need to fetch configuration from a ConfigService, you can use registerAsync.
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { RedisModule } from './redis/redis.module'; // Adjust path as needed
@Module({
imports: [
ConfigModule.forRoot(),
RedisModule.registerAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
host: configService.get<string>('REDIS_HOST'),
port: configService.get<number>('REDIS_PORT'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}To make the RedisService available in all modules without importing RedisModule everywhere, you can register it as a global module.
// app.module.ts
import { Module } from '@nestjs/common';
import { RedisModule } from './redis/redis.module'; // Adjust path as needed
@Module({
imports: [
RedisModule.register({
isGlobal: true,
host: 'localhost',
port: 6379,
}),
],
})
export class AppModule {}Once the module is imported, you can inject RedisService into any of your services or controllers and start using it.
import { Injectable } from '@nestjs/common';
import { RedisService } from './redis/redis.service'; // Adjust path as needed
@Injectable()
export class MyService {
constructor(private readonly redisService: RedisService) {}
async getCachedData(key: string) {
const cached = await this.redisService.get<string>(key);
if (cached) {
return cached;
}
// ... logic to fetch and set data
}
async cacheData(key: string, value: any) {
// Set with a TTL of 1 hour
await this.redisService.set(key, value, 3600);
}
}The NestJS Redis Module includes a caching interceptor that automatically caches method responses based on configurable parameters.
To use the interceptor, you need to import the RedisModule on the scope of the usage unless the module is set to global.
To use the Redis interceptor in a controller or service method, apply the @UseInterceptors() decorator:
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { RedisCacheInterceptor } from './redis/redis-cache.interceptor'; // Adjust path as needed
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
@UseInterceptors(RedisCacheInterceptor)
async getUser(@Param('id') id: string) {
// This response will be cached automatically
return this.userService.findById(id);
}
}You can customize the cache behavior using the @CacheKey and @CacheTTL decorators:
this is not recommended since we are going to autotune the ttl per endpoint using ai
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { RedisCacheInterceptor, CacheKey, CacheTTL } from './redis'; // Adjust path as needed
@Controller('products')
export class ProductController {
@Get(':id')
@UseInterceptors(RedisCacheInterceptor)
@CacheKey('product') // Custom cache key prefix
@CacheTTL(7200) // Cache for 2 hours (in seconds)
async getProduct(@Param('id') id: string) {
return this.productService.findById(id);
}
}When using the interceptors you get usefull headers in the response to help you debug and understand the caching behavio:
- x-cache: returns
HITif the response was served from cache,MISSif it was not cached,ERRORif an error occured ,orBYPASSif caching was bypassed (e.g., due to the@NoCachedecorator).
You can also bypass the cache on the client side by sending the header cache-Control: no-cache with your request.
To use the Redis interceptor globally across your entire application, you need to provide it using the APP_INTERCEPTOR token in your module:
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { RedisModule } from './redis/redis.module';
import { RedisCacheInterceptor } from './redis/redis-cache.interceptor';
@Module({
imports: [
RedisModule.register({
isGlobal: true,
host: 'localhost',
port: 6379,
}),
],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: RedisCacheInterceptor,
},
],
})
export class AppModule {}You can conditionally enable/disable caching using the @NoCache decorator:
import { Controller, Get, Post } from '@nestjs/common';
import { NoCache } from './redis/decorators'; // Adjust path as needed
@Controller('analytics')
export class AnalyticsController {
@Get('stats')
// This will be cached (assuming global interceptor is enabled)
async getStats() {
return this.analyticsService.calculateStats();
}
@Post('track')
@NoCache() // This will bypass caching
async trackEvent(@Body() event: any) {
return this.analyticsService.trackEvent(event);
}
}The interceptor works seamlessly with the RedisService for manual cache invalidation: In here you are supposed to use the custom key you genereated earlier with the @CacheKey decorator If you cannot part with eventual consistency issues and you need to invalidate the cache after each update , it would be better to handle it manually and set the route to @NoCache instead
@Injectable()
export class UserService {
constructor(private readonly redisService: RedisService) {}
async updateUser(id: string, userData: any) {
const updatedUser = await this.userRepository.update(id, userData);
// Invalidate cached user data
await this.redisService.del(`user:${id}`);
return updatedUser;
}
}RedisService provides a wide range of methods to interact with Redis. All methods that store complex data types (like objects or arrays) will automatically JSON.stringify them, and methods that retrieve them will JSON.parse them.
set<T>(key: string, value: T, ttl?: number): Promise<void>: Sets a key-value pair, with an optional TTL in seconds.get<T>(key: string): Promise<T | null>: Retrieves a value by its key.del(key: string): Promise<void>: Deletes a key.ttl(key: string): Promise<number>: Returns the remaining time to live of a key that has a timeout.deleteByPattern(pattern: string): Promise<number>: Deletes all keys matching a given pattern.existsByPattern(pattern: string): Promise<boolean>: Checks if any key matching the pattern exists.getByPattern<T>(pattern: string): Promise<T[]>: Gets all values from keys matching a pattern.
rPush<T>(key: string, value: T): Promise<void>: Appends a value to the end of a list.lPush<T>(key: string, value: T): Promise<void>: Prepends a value to the beginning of a list.lPop<T>(key: string): Promise<T | null>: Removes and returns the first element of a list.rPop<T>(key: string): Promise<T | null>: Removes and returns the last element of a list.getRange<T>(key: string, start: number = 0, end: number = -1): Promise<Array<T>>: Returns a range of elements from a list.
subscribe(channel: string, callback: (message: string) => void | Promise<void>): Subscribes to a channel and executes a callback on message.publish(channel: string, message: string): Publishes a message to a channel.
sAdd(key: string, value: string): Promise<number>: Adds a member to a set.sIsMember(key: string, value: string): Promise<number>: Checks if a member exists in a set.sMembers(key: string): Promise<string[]>: Returns all members of a set.sRem(key: string, ...members: string[]): Promise<number>: Removes one or more members from a set.sCard(key: string): Promise<number>: Returns the number of members in a set.
hSet(key: string, field: string, value: string | number): Promise<number>: Sets a field in a hash.hGet(key: string, field: string): Promise<string | null>: Gets the value of a field in a hash.hGetAll(key: string): Promise<Record<string, string>>: Gets all fields and values in a hash.hIncrBy(key: string, field: string, increment: number = 1): Promise<number>: Increments the integer value of a hash field by the given number.hDel(key: string, ...fields: string[]): Promise<number>: Deletes one or more hash fields.
zAdd(key: string, members: Array<{ score: number; value: string }>): Promise<number>: Adds one or more members to a sorted set.zRem(key: string, ...members: string[]): Promise<number>: Removes one or more members from a sorted set.zRevRangeWithScores(key: string, start: number, stop: number): Promise<Array<{ score: number; value: string }>>: Returns a range of members in a sorted set, by index, with scores, ordered from high to low.
The module configuration is passed directly to ioredis. You can use any valid RedisOptions from the ioredis library.
In addition to the ioredis options, the register and registerAsync methods accept the following extra parameter:
isGlobal(boolean): Iftrue, theRedisServicewill be provided globally. Defaults tofalse.