Caching: Improving Performance

Implementing caching strategies using Redis or other caching providers to improve application performance.


Implementing Redis Caching in NestJS Services

This document demonstrates how to implement caching within NestJS services using Redis, focusing on caching frequently accessed data or expensive operations. Caching improves application performance by reducing the load on underlying data sources and speeding up response times.

Prerequisites

Before you begin, ensure you have the following installed:

  • Node.js (>= 16.x)
  • npm or yarn
  • Redis server running locally or accessible remotely

Installation

First, create a new NestJS project (if you don't already have one):

nest new nestjs-redis-caching

Next, install the necessary packages:

npm install @nestjs/cache-manager cache-manager redis ioredis --save

or with yarn:

yarn add @nestjs/cache-manager cache-manager redis ioredis

Configuration

Import and configure the CacheModule in your AppModule or the module where you plan to use caching. We'll use Redis as the storage provider.

 // app.module.ts
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { RedisClientOptions } from 'redis';
import * as redisStore from 'cache-manager-redis-store';

@Module({
  imports: [
    CacheModule.registerAsync({
      useFactory: async () => ({
        store: redisStore,
        host: 'localhost', // Redis host
        port: 6379,        // Redis port
        ttl: 60,          // Time-to-live in seconds (optional)
      }),
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {} 

Note: Replace 'localhost' and 6379 with your Redis server's actual host and port if different. The ttl option sets the default expiration time for cached items in seconds. Consider using environment variables for host, port, and other configuration values.

Implementing Caching in a Service

Now, let's demonstrate how to use caching within a NestJS service. We'll create a simple service that fetches data from an external API (you can replace this with any expensive operation). First, create a service:

nest generate service example

Then, inject the CacheManager into your service and use it to cache the API response.

 // example.service.ts
import { Injectable, Inject, CACHE_MANAGER } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { HttpService } from '@nestjs/axios'; // You'll need to install @nestjs/axios
import { firstValueFrom } from 'rxjs';

@Injectable()
export class ExampleService {
  constructor(
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
    private readonly httpService: HttpService,
  ) {}

  async getData(): Promise<any> {
    const cachedData = await this.cacheManager.get('example_data');

    if (cachedData) {
      console.log('Returning data from cache');
      return cachedData;
    }

    console.log('Fetching data from API');
    const response = await firstValueFrom(this.httpService.get('https://jsonplaceholder.typicode.com/todos/1')); // Replace with your API endpoint
    const data = response.data;

    await this.cacheManager.set('example_data', data, { ttl: 30 }); // Cache for 30 seconds

    return data;
  }

  async clearCache(): Promise {
    await this.cacheManager.del('example_data');
    console.log("Cache cleared");
  }
} 

Important: Make sure you install @nestjs/axios and rxjs: npm install @nestjs/axios rxjs

Explanation:

  • We inject the CACHE_MANAGER using @Inject.
  • The getData function first checks if the data exists in the cache using cacheManager.get('example_data').
  • If the data is found in the cache, it's returned directly.
  • If the data is not found, it's fetched from the external API using @nestjs/axios.
  • The fetched data is then stored in the cache using cacheManager.set('example_data', data, { ttl: 30 }), with a TTL (time-to-live) of 30 seconds.
  • An example function clearCache is added to demonstrate how to manually invalidate and clear a specific cache entry.

Using the Service in a Controller

Now, let's use the service in a controller to expose an endpoint that retrieves the cached data.

nest generate controller example

Update the controller:

 // example.controller.ts
import { Controller, Get, Delete } from '@nestjs/common';
import { ExampleService } from './example.service';

@Controller('example')
export class ExampleController {
  constructor(private readonly exampleService: ExampleService) {}

  @Get('data')
  async getData(): Promise<any> {
    return this.exampleService.getData();
  }

  @Delete('cache')
  async clearCache(): Promise {
    return this.exampleService.clearCache();
  }
} 

Testing

Run your NestJS application:

npm run start:dev

Then, access the endpoint in your browser or using a tool like curl:

curl http://localhost:3000/example/data

The first request will fetch data from the API and store it in the cache. Subsequent requests within the TTL will retrieve the data from the cache. Call the delete endpoint to clear the cache:

curl -X DELETE http://localhost:3000/example/cache

Advanced Usage

Custom Cache Keys: Use a custom cache key based on input parameters to cache different results for different inputs.

Cache Invalidation: Implement strategies for invalidating the cache when the underlying data changes.

Error Handling: Add error handling to gracefully handle cache-related errors.

Conclusion

This guide provides a basic example of implementing Redis caching in NestJS services. By caching frequently accessed data or expensive operations, you can significantly improve the performance of your application.