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.
| Method | What 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.