Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Caching

A cache is temporary storage for ready results. If the post list is requested hundreds of times a minute but rarely changes, there is no need to hit the database each time: the result can be remembered for a minute and served from memory. NestJS has the @nestjs/cache-manager module for this.

Installation and setup

npm i @nestjs/cache-manager cache-manager
// src/app.module.ts
import { CacheModule } from '@nestjs/cache-manager';
import { Module } from '@nestjs/common';
import { PostsModule } from './posts/posts.module';
 
@Module({
  imports: [CacheModule.register({ isGlobal: true, ttl: 60_000 }), PostsModule],
})
export class AppModule {}

ttl (time to live) is how many milliseconds an entry is kept: one minute here. By default the cache lives in the process memory. isGlobal: true makes the cache available in every module.

Caching responses

The CacheInterceptor interceptor remembers responses of GET routes:

// src/posts/posts.controller.ts
import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager';
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { PostsService } from './posts.service';
 
@Controller('posts')
@UseInterceptors(CacheInterceptor)
export class PostsController {
  constructor(private readonly postsService: PostsService) {}
 
  @Get()
  @CacheTTL(30_000)
  findAll() {
    return this.postsService.findAll(1);
  }
}

The first request to /posts runs the method and stores the response; for the next 30 seconds the response comes from the cache without touching the database. The entry key is the request address, so /posts?page=2 is cached separately. The @CacheTTL() decorator overrides the lifetime for one route. The interceptor does not cache POST, PATCH and DELETE requests.

Working with the cache by hand

A service can read and write the cache itself. For that you inject the object under the CACHE_MANAGER key:

// src/posts/posts.service.ts
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Cache } from 'cache-manager';
import { Repository } from 'typeorm';
import { Post } from './entities/post.entity';
 
@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Post)
    private readonly postsRepository: Repository<Post>,
    @Inject(CACHE_MANAGER) private readonly cache: Cache,
  ) {}
 
  async countPosts(): Promise<number> {
    const cached = await this.cache.get<number>('posts:count');
    if (cached !== undefined && cached !== null) {
      return cached;
    }
 
    const count = await this.postsRepository.count();
    await this.cache.set('posts:count', count, 60_000);
    return count;
  }
}

@Inject() is needed because CACHE_MANAGER is not a class but a string key. The get(), set() and del() methods are asynchronous. Call countPosts() from a GET /posts/count route — the first request hits the database, and the following ones within a minute get the number from the cache.

MethodWhat it does
get(key)Returns the value or an empty result
set(key, value, ttl)Stores the value for ttl milliseconds
del(key)Deletes the entry

Common mistake

After a post is created or changed, the list is still the old one: the response sits in the cache until ttl runs out. When data changes, delete the related entries — for example, call await this.cache.del('/posts') in the create() method. Cache only data where a short update delay is acceptable.

Thanks for reading!

Donate to the site — we work hard for you.