Return a cached observable before calling next.handle() to bypass the controller handler entirely. Calling next.handle() is what actually invokes the handler — returning an observable without calling it means the handler is skipped. Store the result with tap() after the first real invocation.
next.handle() is a lazy Observable — calling it is what invokes the controller handler.
Returning of(cachedValue) before calling next.handle() skips the handler entirely.
tap() is used to store the result after the first real handler execution.
This is the exact mechanism behind NestJS @nestjs/cache-manager CacheInterceptor.
For production caching use @nestjs/cache-manager with TTL and Redis backend.