Overview
Cache GET responses with configurable TTL. Includes cache invalidation service.
Features
- @CacheTTL decorator
- @NoCache decorator
- Key generation
- Invalidation service
Installation
npx hanma add cache-interceptor --framework nest
Dependencies
@nestjs/cache-manager, cache-manager
Code
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Inject,
SetMetadata,
} from "@nestjs/common";
import { Observable, of, tap } from "rxjs";
import { CACHE_MANAGER } from "@nestjs/cache-manager";
import { Cache } from "cache-manager";
import { Reflector } from "@nestjs/core";
// Metadata keys
export const CACHE_TTL_KEY = "cache_ttl";
export const CACHE_KEY_PREFIX = "cache_key_prefix";
export const NO_CACHE_KEY = "no_cache";
/**
* Set cache TTL for a route (in seconds).
*/
export const CacheTTL = (ttl: number) => SetMetadata(CACHE_TTL_KEY, ttl);
/**
* Set custom cache key prefix.
*/
export const CacheKey = (prefix: string) => SetMetadata(CACHE_KEY_PREFIX, prefix);
/**
* Skip caching for a route.
*/
export const NoCache = () => SetMetadata(NO_CACHE_KEY, true);
@Injectable()
export class HttpCacheInterceptor implements NestInterceptor {
private readonly defaultTtl: number = 60; // seconds
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache,
private reflector: Reflector
) {}
async intercept(
context: ExecutionContext,
next: CallHandler
): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest();
// Skip caching for non-GET requests
if (request.method !== "GET") {
return next.handle();
}
// Check if caching is disabled for this route
const noCache = this.reflector.getAllAndOverride<boolean>(NO_CACHE_KEY, [
context.getHandler(),
context.getClass(),
]);
if (noCache) {
return next.handle();
}
// Generate cache key
const cacheKey = this.generateCacheKey(context, request);
// Try to get from cache
const cachedResponse = await this.cacheManager.get(cacheKey);
if (cachedResponse) {
return of(cachedResponse);
}
// Get TTL from decorator or use default
const ttl =
this.reflector.getAllAndOverride<number>(CACHE_TTL_KEY, [
context.getHandler(),
context.getClass(),
]) || this.defaultTtl;
// Execute handler and cache response
return next.handle().pipe(
tap(async (response) => {
await this.cacheManager.set(cacheKey, response, ttl * 1000);
})
);
}
private generateCacheKey(context: ExecutionContext, request: any): string {
const prefix =
this.reflector.get<string>(CACHE_KEY_PREFIX, context.getHandler()) ||
context.getClass().name;
const queryString = request.url.includes("?")
? request.url.split("?")[1]
: "";
// Include user ID for authenticated requests
const userId = request.user?.id || "anonymous";
return `${prefix}:${request.method}:${request.path}:${queryString}:${userId}`;
}
}
// Cache invalidation helper
@Injectable()
export class CacheService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async invalidate(pattern: string): Promise<void> {
// Note: Pattern-based deletion depends on cache store implementation
// For Redis, you might need to use scan + del
await this.cacheManager.del(pattern);
}
async invalidateByPrefix(prefix: string): Promise<void> {
const store = this.cacheManager.store;
if ("keys" in store && typeof store.keys === "function") {
const keys = await store.keys(`${prefix}*`);
await Promise.all(keys.map((key: string) => this.cacheManager.del(key)));
}
}
async get<T>(key: string): Promise<T | undefined> {
return this.cacheManager.get<T>(key);
}
async set(key: string, value: any, ttl?: number): Promise<void> {
await this.cacheManager.set(key, value, ttl);
}
}
Usage
@CacheTTL(300)
@Get('popular')
getPopular() {}
Dependencies
@nestjs/cache-managercache-manager
Frequently Asked Questions
How do I install Cache Interceptor?
Run the following command:
npx hanma add cache-interceptor --framework nestWhat dependencies are required?
This snippet requires: @nestjs/cache-manager, cache-manager. Make sure to install them before using the snippet.
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to
src/interceptors/cache.interceptor.ts.Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.