Introduction

Caching is essential for improving performance and reducing database load. With new update on the nestjs , there are few breaking changes modules. One of them is caching. They have migrated to Keyv

In this post, I’ll show how to integrate Redis caching using Keyv in a NestJS application. Keyv is a simple key-value storage adapter that supports multiple backends, including Redis.

First, install Keyv and the Redis adapter:

npm install keyv @keyv/redis

And have a seperate module for cache as below

import Keyv from 'keyv';
import KeyvRedis from '@keyv/redis';
import { CacheService } from './cache.service';

@Module({
  providers: [
    {
      provide: 'CACHE_INSTANCE',
      useFactory: () => {
       // Connect to Redis
        const redisStore = new KeyvRedis('redis://localhost:6379');
        return new Keyv({ store: redisStore, namespace: '' });
      },
    },
    CacheService,
  ],
  exports: [CacheService, 'CACHE_INSTANCE'],
})
export class CacheModule {}

Creating a cache services

import Keyv from 'keyv';

@Injectable()
export class CacheService {
  constructor(@Inject('CACHE_INSTANCE') private readonly cache: Keyv) {}

  async get<T>(key: string): Promise<T | undefined> {
    console.log(`Getting from Redis: ${key}`);
    const value = await this.cache.get<string>(key);
    if (!value) return undefined;

    try {
      return JSON.parse(value) as T; 
    } catch (error) {
      console.error('Failed to parse cache value:', error);
      return value as T; 
    }
  }

  async set<T>(key: string, value: T, ttl?: number): Promise<void> {
    console.log(`Storing in Redis: ${key} =>`, JSON.stringify(value));
    await this.cache.set(key, JSON.stringify(value), ttl);
  }

  async delete(key: string): Promise<void> {
    console.log(`Deleting from Redis: ${key}`);
    await this.cache.delete(key);
  }
}

And having those in place we can use this forany other services , in this case I have a simple user service where I query users from database and store it in the cache.

import { PrismaService } from 'src/prisma.service';
import { CacheService } from 'src/cache/cache.service';
import { User } from '@prisma/client';

@Injectable()
export class UserService {
  constructor(
    private prisma: PrismaService,
    private readonly cacheService: CacheService,
  ) {}

  async getUsers(page: number, limit: number): Promise<User[]> {
    const cacheKey = `all_users_page_${page}_limit_${limit}`; 
    const cachedUsers = await this.cacheService.get<User[]>(cacheKey);

    if (cachedUsers) {
      console.log('Returning users from cache:', cacheKey);
      return cachedUsers;
    }

    const skip = (page - 1) * limit;

    const users = await this.prisma.user.findMany({
      skip,
      take: limit,
      orderBy: { id: 'asc' },
      include: { country: true },
    });

    console.log('Fetching from DB and caching result:', cacheKey);
    await this.cacheService.set(cacheKey, users, 60000); 

    return users;
  }
}

Below is the user controller

import { UserService } from './user.service';
import { ParseIntPipe } from '@nestjs/common';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  async findAll(
    @Query('page', ParseIntPipe) page: number,
    @Query('limit', ParseIntPipe) limit: number,
  ) {
    return this.userService.getUsers(page, limit);
  }
}

You can check Kevy documention here for details explanation.

Happy coding ✌️