typescript 122 lines · 4 tabs

Per-Route Response Caching in NestJS with a Custom CacheInterceptor and Cache Key Builder

Shared by codesnips Aug 2026
4 tabs
import { SetMetadata } from '@nestjs/common';

export const CACHEABLE_KEY = 'cacheable:options';

export type VaryDimension = 'user' | 'tenant' | 'query';

export interface CacheableOptions {
  ttl: number; // seconds
  varyBy?: VaryDimension[];
}

export const Cacheable = (options: CacheableOptions) =>
  SetMetadata(CACHEABLE_KEY, {
    ttl: options.ttl,
    varyBy: options.varyBy ?? [],
  });
4 files · typescript Explain with highlit

This snippet shows how to build per-route response caching in NestJS without relying entirely on the built-in CacheModule defaults, giving full control over which routes are cached, for how long, and how the cache key is derived from the request. The pattern is useful when a handful of read-heavy endpoints run expensive queries whose results are safe to serve slightly stale, and where the default global cache key (just the URL) is too coarse because the response also depends on the authenticated user, query parameters, or a tenant header.

In cacheable.decorator.ts, a @Cacheable decorator attaches metadata to the handler using SetMetadata. It carries a TTL and an optional varyBy list of request attributes, so caching becomes an explicit, opt-in choice on each route rather than an all-or-nothing global setting. Only handlers annotated with this decorator are ever cached; everything else passes straight through.

cache-key.builder.ts centralizes key construction. CacheKeyBuilder.build combines the HTTP method, path, and a normalized query string, then folds in any varyBy dimensions such as the user id or a tenant header. Query params are sorted so ?a=1&b=2 and ?b=2&a=1 collapse to the same key, avoiding accidental cache misses. The long composite string is hashed with createHash('sha1') to keep Redis keys compact and free of unsafe characters.

cache.interceptor.ts ties it together. HttpCacheInterceptor reads the metadata with Reflector; when absent it returns next.handle() untouched. It also skips non-GET requests, since caching mutations would be dangerous. On a hit it short-circuits with of(cached), never invoking the handler. On a miss it lets the handler run and uses the RxJS tap operator to write the result back with the configured TTL. Because tap runs on the success path only, error responses are never cached.

The main trade-off is staleness: callers may receive data up to ttl seconds old, so this fits reference or aggregate data, not fast-changing balances. A subtle pitfall is caching per-user data under a shared key, which varyBy prevents. Reflect.getMetadata combined with SetMetadata is the idiomatic NestJS way to make cross-cutting behavior configurable per handler.


Related snips

typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
ruby
Rails.application.configure do
  config.after_initialize do
    Bullet.enable = true
    Bullet.alert = false
    Bullet.bullet_logger = true
    Bullet.console = true

N+1 query detection with Bullet gem

rails performance activerecord
by Alex Kumar 2 tabs
ruby
json.array! @posts do |post|
  json.cache! ['v1', post], expires_in: 1.hour do
    json.id post.id
    json.title post.title
    json.excerpt post.excerpt
    json.published_at post.published_at

Fragment caching for expensive JSON serialization

rails caching performance
by Alex Kumar 1 tab
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Per-Route Response Caching in NestJS with a Custom CacheInterceptor and Cache Key Builder — share card
Link copied