Cache Interceptors
Cache Interceptors provide a declarative, automatic way to cache HTTP responses in NestJS using decorators, eliminating the need to manually write .get() and .set() logic in your services.
Overview
While injecting CACHE_MANAGER gives you granular control, it pollutes your business logic with caching boilerplate.
NestJS provides a built-in CacheInterceptor. When applied to a controller route, it intercepts the incoming HTTP request. It checks if a cached response exists for the URL; if yes, it returns it immediately. If not, it lets the controller execute, catches the response on the way out, saves it to the cache, and then returns it to the user.
Key Concepts
@UseInterceptors(CacheInterceptor): The decorator used to enable automatic caching on a controller or specific route.- Auto-generated Cache Keys: By default, the
CacheInterceptoruses the HTTP request URL (e.g.,/users/123) as the cache key. - GET requests only: By default, the interceptor only caches
GETrequests. It ignoresPOST,PUT,PATCH, andDELETE.
Code Examples
1. Auto-Caching a Route
Simply apply the interceptor. Ensure CacheModule is registered in your application.
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager';
@Controller('stats')
@UseInterceptors(CacheInterceptor) // Applies to all routes in this controller
export class StatsController {
@Get('global')
// Automatically caches the response under the key '/stats/global'
getGlobalStats() {
return { users: 1000, revenue: 5000 };
}
@Get('heavy')
@CacheTTL(10000) // Override the default TTL just for this route (10 seconds)
getHeavyQuery() {
return this.expensiveDatabaseCall();
}
}
2. Custom Cache Keys
If you want to cache a response, but don’t want the key to be the raw URL, you can use the @CacheKey() decorator.
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager';
@Controller('config')
export class ConfigController {
@Get()
@UseInterceptors(CacheInterceptor)
@CacheKey('app_global_configuration') // The exact string used in Redis/Memory
@CacheTTL(3600000) // 1 hour
getConfig() {
return { theme: 'dark', language: 'en' };
}
}
3. Dynamic Custom Keys (Custom Interceptor)
Sometimes the URL isn’t enough. What if the response depends on the Authorization header (User A sees different data than User B on the same URL)? You must subclass the CacheInterceptor and override trackBy.
import { Injectable, ExecutionContext, CallHandler } from '@nestjs/common';
import { CacheInterceptor } from '@nestjs/cache-manager';
@Injectable()
export class HttpUserCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
const request = context.switchToHttp().getRequest();
const userId = request.user?.id;
const url = request.url;
// If there is no user, skip caching entirely by returning undefined
if (!userId) {
return undefined;
}
// Cache key: userId-123:/dashboard
return `userId-${userId}:${url}`;
}
}
Apply it like this: @UseInterceptors(HttpUserCacheInterceptor)
Best Practices
- Beware of Query Parameters: By default,
/users?sort=ascand/users?sort=descwill generate different cache keys because the URLs are different. This is usually what you want. However, if a user sends/users?random=12345, it generates a new cache entry, which can easily be abused to flood your cache (Cache Poisoning / Memory Exhaustion). - Don’t Cache Authenticated Routes Globally: If you apply
CacheInterceptorto a route that returns personal user data (like/profile), the first person who hits it will cache their profile under the key/profile. The next person who hits/profilewill see the first person’s data! You MUST use a customtrackBymethod (as shown in Example 3) to append the User ID to the cache key for authenticated routes.