Кеширование
Кеш — временное хранилище готовых результатов. Если список статей запрашивают сотни раз в минуту, а меняется он редко, незачем каждый раз обращаться к базе: результат можно запомнить на минуту и отдавать из памяти. В NestJS для этого есть модуль @nestjs/cache-manager.
Установка и подключение
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, «время жизни») — сколько миллисекунд хранится запись: здесь одна минута. По умолчанию кеш лежит в памяти процесса. isGlobal: true делает кеш доступным во всех модулях.
Кеширование ответов
Перехватчик CacheInterceptor запоминает ответы GET-маршрутов:
// 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);
}
}Первый запрос к /posts выполнит метод и сохранит ответ, следующие 30 секунд ответ будет приходить из кеша без обращения к базе. Ключ записи — адрес запроса, поэтому /posts?page=2 кешируется отдельно. Декоратор @CacheTTL() переопределяет время жизни для одного маршрута. Запросы POST, PATCH и DELETE перехватчик не кеширует.
Ручная работа с кешем
Сервис может сам читать и записывать кеш. Для этого внедряют объект по ключу CACHE_MANAGER:
// 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() нужен потому, что CACHE_MANAGER — не класс, а строковый ключ. Методы get(), set() и del() асинхронные. Вызовите countPosts() из маршрута GET /posts/count — первый запрос обратится к базе, а следующие в течение минуты получат число из кеша.
| Метод | Что делает |
|---|---|
get(key) | Возвращает значение или пустой результат |
set(key, value, ttl) | Сохраняет значение на ttl миллисекунд |
del(key) | Удаляет запись |
Частая ошибка
После создания или изменения статьи список по-прежнему старый: ответ лежит в кеше, пока не истечёт ttl. Когда данные меняются, удаляйте связанные записи — например, вызовите await this.cache.del('/posts') в методе create(). Кешируйте только то, где небольшая задержка обновления допустима.