How to Implement Caching in NestJS

Updated: December 31, 2023 By: Guest Contributor Post a comment

Introduction

Caching is a critical component in web applications to enhance user experience and system efficiency. In this tutorial, we’ll explore how to integrate caching mechanisms into a NestJS application to improve performance by reducing unnecessary data fetching. We’ll start simple and delve into more sophisticated methods as we progress.

Setting Up the Basic NestJS Project

Before we can implement caching, you need to have a basic NestJS project set up. You can create a new project using the Nest CLI:


$ npm i -g @nestjs/cli
$ nest new project-name

Once you have your project ready, we’ll install the caching module:


$ npm install cache-manager

Let’s start by configuring a simple in-memory cache.


import { Module, CacheModule } from '@nestjs/common';

@Module({
  imports: [
    CacheModule.register({
      ttl: 5, // seconds
      max: 100 // maximum number of items in cache
    }),
  ],
})
export class AppModule {}

This code will create a cache that holds a maximum of 100 items, with each item living for 5 seconds.

Using Cache in a Service

Next, let’s see how to use the cache in a service. We’ll create a service that utilizes the CACHE_MANAGER injection token to interact with the cache.


import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common';
import { Cache } from 'cache-manager';

@Injectable()
export class YourService {
  constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

  async getSomeData() {
    const cachedData = await this.cacheManager.get('data-key');
    if (cachedData) {
      return cachedData;
    }

    const newData = '...'; // Here you'd get new data from a database or other source
    await this.cacheManager.set('data-key', newData);
    return newData;
  }
}

This service checks for cached data and returns it if available; otherwise, it fetches new data, caches it, and then returns the new data.

Advanced Caching Strategies

For more complex applications, you can utilize cache stores like Redis with NestJS. First, install the necessary packages:


$ npm install cache-manager-redis-store

Then update your cache configuration in the AppModule:


CacheModule.register({
  store: 'redis',
  host: 'localhost',
  port: 6379,
  ttl: 120,
  max: 1000
})

This enables your application to connect to a Redis store, providing a fast and resilient caching layer.

Cache Decorators and Interceptors

You can further streamline caching in NestJS by using provided decorators and interceptors. An example is the @Cacheable() decorator that simplifies caching of method responses.


@Cacheable(options)
async getItems() {
  // ... your code to retrieve items
}

By applying the @CacheTTL() decorator, you can specify a custom TTL for this method’s cache entries:


@CacheTTL(10) // TTL in seconds
@Cacheable(options)
async getItems() {
  // ... your code to retrieve items
}

Nest also provides the @CacheInterceptor, which you can apply at the controller method level to cache entire HTTP responses:


@UseInterceptors(CacheInterceptor)
@Get()
async getItems() {
  // ... return your items
}

Keep in mind that advanced decorators and interceptors may need additional setup inside your NestJS application depending on your requirements.

Improving Performance with Cache Strategies

You can create custom cache managers or strategies to handle more specific needs. For example, a Time-To-Live (TTL) strategy can be implemented for different routes or data sets:


export class CustomCacheManager {
  // ... custom caching logic here
}

Incorporating cache strategies tailored to your data’s nature and usage patterns can notably improve performance.

Handling Cache Eviction and Consistency

Correct cache eviction is essential to avoid presenting stale data to clients. Use Nest’s built-in functionality or create custom logic to refresh the cache periodically:


await this.cacheManager.del('data-key');
await this.cacheManager.reset();

Ensuring cache consistency, especially in distributed systems, is crucial. This may involve setting up pub/sub mechanisms with your cache store or implementing strategies for cache invalidation.

Monitoring and Debugging Cache Issues

Implement logging and monitoring to observe cache behavior and troubleshoot issues. Here’s an example showing how to log cache operations:


console.log(`Cache miss for key: ${key}`);

Observability tools or modules, such as the Health Checks module in NestJS, can also provide insights into cache health and performance.

Conclusion

Caching in NestJS can significantly improve your application performance when used correctly. Start with a simple in-memory cache, move to more persistent solutions like Redis for production, and fine-tune with strategies and evictions for keeping your cache optimized. Happy caching!