Что такое CacheModule и кеширование в NestJS?
Что такое CacheModule
CacheModule — встроенный модуль NestJS для кеширования данных, построенный поверх пакета cache-manager. Он даёт единый API для сохранения результатов вычислений или запросов в памяти процесса или во внешнем хранилище (Redis, Memcached), чтобы не выполнять повторно дорогие операции.
Подключение и конфигурация
Модуль подключается через CacheModule.register() или CacheModule.registerAsync(), если конфигурация зависит от ConfigService или другого провайдера. По умолчанию используется in-memory хранилище с TTL и лимитом на количество записей.
@Module({
imports: [
CacheModule.register({
ttl: 5000, // время жизни записи, мс
max: 100, // максимум записей в кеше
}),
],
})
export class AppModule {}
В production обычно подключают внешнее хранилище через cache-manager-redis-store, чтобы кеш переживал рестарт приложения и был общим для нескольких инстансов сервиса.
Кеширование через CacheInterceptor
Самый простой способ — глобальный или локальный CacheInterceptor, который автоматически кеширует ответы GET-эндпоинтов, используя URL как ключ.
@Controller('posts')
@UseInterceptors(CacheInterceptor)
export class PostsController {
@Get()
@CacheTTL(30) // переопределяем TTL для конкретного маршрута
findAll() {
return this.postsService.findAll();
}
}
Ручная работа с кешем через CACHE_MANAGER
Когда нужен более гибкий контроль — условное кеширование, инвалидация, кеширование не только HTTP-ответов, — кеш-менеджер инжектируется напрямую.
@Injectable()
export class PostsService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async findOne(id: string) {
const cached = await this.cacheManager.get(`post:${id}`);
if (cached) return cached;
const post = await this.repository.findOneBy({ id });
await this.cacheManager.set(`post:${id}`, post, 60000);
return post;
}
async update(id: string, dto: UpdatePostDto) {
const post = await this.repository.save({ id, ...dto });
await this.cacheManager.del(`post:${id}`); // инвалидация при изменении
return post;
}
}
Ключевые нюансы
CacheInterceptorпо умолчанию кеширует только GET-запросы и работает по ключу на основе URL — динамические query-параметры нужно учитывать вручную либо отключать интерцептор для таких маршрутов.- TTL и ключ настраиваются как глобально, так и точечно через
@CacheTTLи@CacheKey. - Для распределённых систем нужен внешний стор (Redis), иначе у каждого инстанса приложения будет свой собственный кеш.
- Инвалидация — ответственность разработчика: NestJS не отслеживает изменения данных автоматически, при мутациях кеш нужно сбрасывать вручную.
Что хочет услышать интервьюер
Понимание, что CacheModule построен на cache-manager и даёт единый API для разных хранилищ (memory, Redis, Memcached)
Знание способов использования: CacheInterceptor для авто-кеширования GET-запросов и CACHE_MANAGER для ручного управления
Понимание, что инвалидация кеша — ручная ответственность разработчика, а не автоматика фреймворка
Знание декораторов @CacheTTL и @CacheKey для тонкой настройки на уровне конкретного роута
Понимание необходимости внешнего хранилища (Redis) для распределённых и многоинстансных приложений
Пример: Подключение CacheModule с in-memory хранилищем
@Module({
imports: [
CacheModule.register({
ttl: 5000, // время жизни записи, мс
max: 100, // максимум записей в кеше
}),
],
})
export class AppModule {}
Пример: Ручное кеширование и инвалидация через CACHE_MANAGER
@Injectable()
export class PostsService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async findOne(id: string) {
const cached = await this.cacheManager.get(`post:${id}`);
if (cached) return cached;
const post = await this.repository.findOneBy({ id });
await this.cacheManager.set(`post:${id}`, post, 60000);
return post;
}
async update(id: string, dto: UpdatePostDto) {
const post = await this.repository.save({ id, ...dto });
await this.cacheManager.del(`post:${id}`); // инвалидация при изменении
return post;
}
}
Типичные ошибки
Кандидат думает, что CacheModule автоматически инвалидирует кеш при изменении данных
Забывают, что CacheInterceptor по умолчанию кеширует только GET-запросы, и пытаются применить его к POST/PUT
Не учитывают, что при нескольких инстансах приложения in-memory кеш не будет общим — нужен внешний стор
Путают глобальный TTL модуля и TTL конкретного маршрута через @CacheTTL, не зная про приоритет настроек
Не задумываются о коллизиях кеш-ключей при динамических query-параметрах, так как по умолчанию ключом служит URL


