Skip to content

Cache Plugin

Caching solution with L1 (Memory) + L2 (Redis) architecture, anti-stampede protection, and tag-based invalidation.

Overview

The Cache Plugin provides a two-tier caching solution for NestJS applications.

ChallengeTraditional ApproachWith Cache Plugin
Slow API responses50-500ms database queries< 1ms from cache
Database overloadEvery request queries DBHigh cache hit rate
Cache stampedeParallel DB calls on expirySingle call with lock coordination
Stale data issuesManual invalidation trackingAutomatic tag-based invalidation
Cold start latencySlow initial requestsPre-warming support

Key Features

  • Two-Tier Architecture — In-memory L1 cache with Redis L2 backend
  • Stampede Protection — Prevents cache avalanche on expiry
  • Stale-While-Revalidate — Serve stale data while refreshing in background
  • Tag-Based Invalidation — Invalidate groups of related cache entries
  • Decorator API@Cached, @Cacheable, @CacheEvict, @CachePut
  • Observability — Prometheus metrics and OpenTelemetry tracing

Why Proxy-Based Decorators

Unlike most NestJS caching libraries that rely on interceptors, RedisX @Cached uses descriptor replacement on the prototype — not a proxy object wrapping the instance. This gives you capabilities that interceptor-based solutions fundamentally cannot provide:

  • Works on any Injectable — services, repositories, workers, cron jobs, RMQ consumers
  • Self-invocation worksthis.getUser() inside the same class goes through cache. No "self-invocation limitation" like Spring or NestJS interceptors
  • No ExecutionContext dependency — caching is not tied to the HTTP request pipeline
  • No interceptor registration — no UseInterceptors(), no controller-only restrictions

How it works

@Cached replaces the method on the class prototype at decoration time. When NestJS creates an instance, the prototype already contains the wrapped version. Every call — including this.method() from the same class — goes through the cache layer automatically.

The Spring-style decorators (@Cacheable, @CacheEvict, @CachePut) are also available for developers who prefer that pattern, but they require DeclarativeCacheInterceptor and only work in controller context.

Installation

bash
npm install @nestjs-redisx/core @nestjs-redisx/cache ioredis
bash
npm install @nestjs-redisx/core @nestjs-redisx/cache redis

Basic Configuration

typescript
import { Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-redisx/core';
import { CachePlugin } from '@nestjs-redisx/cache';

@Module({
  imports: [
    RedisModule.forRoot({
      clients: {
        host: 'localhost',
        port: 6379,
      },
      plugins: [
        new CachePlugin({
          l1: {
            enabled: true,
            maxSize: 1000,
            ttl: 60,             // seconds
          },
          l2: {
            enabled: true,
            defaultTtl: 3600,    // seconds
          },
        }),
      ],
    }),
  ],
})
export class AppModule {}

Usage Example

typescript
import { Injectable } from '@nestjs/common';
import { Cached, CacheEvict } from '@nestjs-redisx/cache';
import { User, UpdateUserDto, UserRepository } from './types';

@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}

  @Cached({
    key: 'user:{0}',
    ttl: 300,
    tags: ['users'],
  })
  async findById(id: string): Promise<User> {
    return this.userRepository.findById(id);
  }

  @CacheEvict({ tags: ['users'] })
  async update(id: string, data: UpdateUserDto): Promise<User> {
    return this.userRepository.update(id, data);
  }
}

Architecture

Performance Characteristics

ScenarioWithout CacheL2 OnlyL1 + L2
Initial request50ms50ms50ms
Same instance50ms~3ms< 1ms
Different instance50ms~3ms~3ms
Post-invalidation50ms50ms50ms

Documentation

Getting StartedReferenceOperations
Core ConceptsDecoratorsMonitoring
ConfigurationService APITesting
Tag InvalidationRecipesTroubleshooting

Released under the MIT License.