npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nathapp/nestjs-cache

v3.0.0

Published

nestjs-cache

Readme

@nathapp/nestjs-cache

Enterprise-grade caching module for NestJS with multi-tier storage, tag-based invalidation, and distributed synchronization.

Built on top of Cacheable and Keyv for high-performance L1/L2 caching.

Features

  • Multiple strategies: Memory, Distributed (Redis), Multi-Tier (L1 + L2), Legacy
  • Tag-based invalidation: O(m) bulk cache clearing using Redis Sets
  • Distributed sync: Automatic cross-instance cache synchronization via CacheableSync
  • Unified API: Single invalidate() method with mode overloads (key, pattern, tag)
  • Redis URI support: Parse redis:// and rediss:// URIs into connection objects
  • Metrics hooks: Monitor hits, misses, sets, deletes, and errors
  • Health checks: Built-in health check interface
  • Namespace support: Isolated cache namespaces with auto-prefixed tags
  • Graceful degradation: Fallback to memory when Redis is unavailable (MULTI_TIER)
  • Non-blocking mode: Background L2 updates for low-latency responses

Installation

npm install @nathapp/nestjs-cache cacheable @keyv/redis
# For distributed sync (MULTI_TIER with multiple instances):
npm install @qified/redis

Quick Start

Memory Strategy (Single Instance)

import { CacheModule, CacheStrategy } from '@nathapp/nestjs-cache';

@Module({
  imports: [
    CacheModule.register({
      strategy: CacheStrategy.MEMORY,
      memory: {
        lruSize: 1000,
        ttl: '5m',
      },
    }),
  ],
})
export class AppModule {}

Distributed Strategy (Redis Only)

CacheModule.register({
  strategy: CacheStrategy.DISTRIBUTED,
  distributed: {
    host: 'localhost',
    port: 6379,
    ttl: '10m',
  },
})

Multi-Tier Strategy (L1 Memory + L2 Redis)

CacheModule.register({
  strategy: CacheStrategy.MULTI_TIER,
  memory: {
    lruSize: 500,
    ttl: '1m',
  },
  distributed: {
    url: 'redis://localhost:6379',
    ttl: '10m',
  },
})

Redis URI Configuration

CacheModule.register({
  strategy: CacheStrategy.DISTRIBUTED,
  distributed: {
    url: 'redis://username:[email protected]:6379/0',
    ttl: '10m',
  },
})

Individual fields override URI-parsed values:

distributed: {
  url: 'redis://localhost:6379',
  port: 6380,  // Overrides port from URI
}

Usage

Basic Cache Operations

import { Injectable } from '@nestjs/common';
import { CacheManager } from '@nathapp/nestjs-cache';

@Injectable()
export class UserService {
  constructor(private readonly cache: CacheManager) {}

  async getUser(id: string) {
    // Get with resolver (cache-aside pattern)
    return this.cache.get(
      ['USER', id],
      async () => this.userRepository.findById(id),
      60000, // TTL in ms
    );
  }

  async updateUser(id: string, data: UpdateUserDto) {
    await this.userRepository.update(id, data);
    // Invalidate cached user
    await this.cache.invalidate(['USER', id]);
  }
}

Tag-Based Invalidation

Tags allow efficient bulk invalidation of related cache entries. Instead of scanning the entire keyspace (O(n)), tags use Redis Sets for O(m) complexity where m = number of tagged keys.

@Injectable()
export class PostService {
  constructor(private readonly cache: CacheManager) {}

  async getPost(userId: string, postId: string) {
    return this.cache.get(
      ['USER', userId, 'POST', postId],
      async () => this.postRepository.findOne(postId),
      { tags: ['USER:' + userId + ':POSTS', 'USER:' + userId] },
    );
  }

  async setPost(userId: string, postId: string, data: Post) {
    await this.cache.set(
      ['USER', userId, 'POST', postId],
      data,
      60000,
      { tags: ['USER:' + userId + ':POSTS', 'USER:' + userId] },
    );
  }

  async deleteAllUserPosts(userId: string) {
    // Invalidate all posts for a user in one call (O(m))
    await this.cache.invalidate(
      ['USER', userId, 'POSTS'],
      { mode: 'tag' },
    );
  }

  async deleteAllUserData(userId: string) {
    // Invalidate everything for a user
    await this.cache.invalidate(
      ['USER', userId],
      { mode: 'tag' },
    );
  }
}

get() Overloads

// Basic get (returns cached value or null)
const value = await cache.get<User>(['USER', '123']);

// Get with resolver (cache-aside)
const user = await cache.get(['USER', '123'], () => fetchUser('123'));

// Get with resolver and explicit TTL
const user = await cache.get(['USER', '123'], () => fetchUser('123'), 60000);

// Get with resolver and tags (default 300s TTL)
const user = await cache.get(['USER', '123'], () => fetchUser('123'), {
  tags: ['USER:123'],
});

// Get with resolver, TTL, and tags
const user = await cache.get(['USER', '123'], () => fetchUser('123'), 60000, {
  tags: ['USER:123'],
});

Unified invalidate() API

// Exact key deletion (default)
await cache.invalidate(['USER', '123']);

// Explicit mode: exact key
await cache.invalidate(['USER', '123'], { mode: 'key' });

// Tag-based invalidation (O(m))
await cache.invalidate(['USER:123:POSTS'], { mode: 'tag' });

// Backward compatibility: group=true now maps to tag mode
await cache.invalidate(['USER', '123'], true);

Batch Invalidation

await cache.mInvalidate([
  { cacheKey: ['USER', '123'], mode: 'key' },
  { cacheKey: ['USER:456:POSTS'], mode: 'tag' },
  { cacheKey: ['SESSION:789'], mode: 'key' },
]);

Namespaced Cache

const postCache = cache.namespace('POSTS');

// All keys are auto-prefixed: POSTS:123
await postCache.set(['123'], postData, 60000, {
  tags: ['AUTHOR:456'],  // Stored as POSTS:AUTHOR:456
});

// Invalidate within namespace
await postCache.invalidate(['AUTHOR:456'], { mode: 'tag' });

// Invalidate all keys in namespace
await postCache.invalidateAll();

Metrics

CacheModule.register({
  strategy: CacheStrategy.DISTRIBUTED,
  distributed: { host: 'localhost', port: 6379 },
  metrics: {
    onCacheHit: (ctx) => console.log(`HIT ${ctx.key} ${ctx.durationMs}ms`),
    onCacheMiss: (ctx) => console.log(`MISS ${ctx.key}`),
    onCacheSet: (ctx) => console.log(`SET ${ctx.key} ttl=${ctx.ttl}`),
    onCacheDelete: (ctx) => console.log(`DEL ${ctx.key}`),
    onCacheError: (ctx) => console.error(`ERR ${ctx.key}`, ctx.error),
  },
})

Health Check

const health = await cache.isHealthy();
// {
//   healthy: true,
//   timestamp: Date,
//   connections: [
//     { name: 'memory', status: 'connected' },
//     { name: 'redis', status: 'connected', latencyMs: 2 }
//   ],
//   stats: { ... }
// }

Distributed Synchronization (CacheableSync)

For multi-instance deployments using MULTI_TIER, cache synchronization is automatically enabled. When one instance sets or deletes a key, all other instances update their L1 (memory) caches via Redis Pub/Sub.

CacheModule.register({
  strategy: CacheStrategy.MULTI_TIER,
  memory: { ttl: '1m' },
  distributed: { host: 'localhost', port: 6379, ttl: '10m' },
  // sync is enabled by default for MULTI_TIER
  // To disable: sync: { enabled: false }
})

To use a custom namespace for sync isolation:

sync: {
  enabled: true,
  namespace: 'my-service',
}

Tag Configuration

CacheModule.register({
  strategy: CacheStrategy.DISTRIBUTED,
  distributed: { host: 'localhost', port: 6379 },
  tags: {
    enabled: true,          // default: true when Redis available
    tagPrefix: '_TAG:',     // prefix for tag sets in Redis
    tagTtl: 86400,          // tag set TTL in seconds (24h)
    maxTagsPerKey: 10,      // max tags per cache entry
    maxKeysPerTag: 10000,   // safety limit per tag set
  },
})

Cache Interceptor

Automatically cache HTTP responses:

import { CacheInterceptor, CacheReqOptions } from '@nathapp/nestjs-cache';

@Controller('users')
@UseInterceptors(CacheInterceptor)
export class UserController {
  @Get(':id')
  @CacheReqOptions({ ttl: 60000 })
  getUser(@Param('id') id: string) {
    return this.userService.findById(id);
  }
}

Strategies Comparison

| Feature | MEMORY | DISTRIBUTED | MULTI_TIER | LEGACY | |---------|--------|------------|------------|--------| | L1 (Memory) | ✅ | ❌ | ✅ | ✅ | | L2 (Redis) | ❌ | ✅ | ✅ | ❌ | | Tag Invalidation | ❌ | ✅ | ✅ | ⚠️ Limited | | Cross-Instance Sync | N/A | N/A | ✅ Auto | ❌ | | Graceful Degradation | N/A | ✅ | ✅ | N/A | | Non-Blocking | N/A | N/A | ✅ | N/A | | Recommended | Dev/Test | Single Redis | Production | Migration only |

Migration from Legacy

Before (Legacy)

import { CacheModule } from '@nathapp/nestjs-cache';

CacheModule.register({
  // No strategy = LEGACY by default
  ttl: 300,
})

After (Enhanced)

import { CacheModule, CacheStrategy } from '@nathapp/nestjs-cache';

CacheModule.register({
  strategy: CacheStrategy.MULTI_TIER,
  memory: { lruSize: 1000, ttl: '5m' },
  distributed: { host: 'localhost', port: 6379, ttl: '10m' },
})

The CacheManager API is backward compatible — get(), set(), invalidate() all work the same way with enhanced strategies.


Deprecated APIs

⚠️ The following APIs are deprecated and will be removed in a future major release. Please migrate to the recommended alternatives.

InvalidateStrategy.BROKER

Deprecated. Cross-instance cache invalidation is now handled by CacheableSync (built into Cacheable).

// ❌ Deprecated
CacheModule.register({
  invalidate: {
    strategy: InvalidateStrategy.BROKER,
    transport: Transport.REDIS,
    options: { host: 'localhost', port: 6379 },
  },
})

// ✅ Replacement — use MULTI_TIER strategy (sync is automatic)
CacheModule.register({
  strategy: CacheStrategy.MULTI_TIER,
  distributed: { host: 'localhost', port: 6379 },
})

CacheInvalidateManager / RedisCacheInvalidateManager

Deprecated. These classes are replaced by CacheableSync for enhanced strategies. They remain functional only for CacheStrategy.LEGACY.

invalidateByTag() / invalidateByTags()

Deprecated. Use the unified invalidate() API with { mode: 'tag' } instead.

// ❌ Deprecated
await cache.invalidateByTag('USER:123:POSTS');
await cache.invalidateByTags(['USER:123:POSTS', 'USER:456:POSTS']);

// ✅ Replacement
await cache.invalidate(['USER:123:POSTS'], { mode: 'tag' });
await cache.mInvalidate([
  { cacheKey: ['USER:123:POSTS'], mode: 'tag' },
  { cacheKey: ['USER:456:POSTS'], mode: 'tag' },
]);

invalidateByPattern()

Deprecated. Pattern-based invalidation uses O(n) SCAN which doesn't scale. Use tag-based invalidation instead.

// ❌ Deprecated — O(n) keyspace scan
await cache.invalidateByPattern('USER:123:*');

// ✅ Replacement — O(m) tag-based
// First, set entries with tags:
await cache.set(['USER', '123', 'POST', '1'], data, 60000, {
  tags: ['USER:123:POSTS'],
});
// Then invalidate by tag:
await cache.invalidate(['USER:123:POSTS'], { mode: 'tag' });

Note: If invalidateByPattern() is called and a tag registry is available, it will automatically redirect to tag-based invalidation with a deprecation warning.

buildRedisUrl()

Deprecated. Use resolveRedisConnection() to parse URIs into connection objects instead of building URL strings.

// ❌ Deprecated
const url = buildRedisUrl({ host: 'localhost', port: 6379 });

// ✅ Replacement
const connection = resolveRedisConnection({ host: 'localhost', port: 6379 });

dipatch() (typo alias)

Deprecated. Use the correctly spelled dispatch() method.

lib/inteceptor/ (typo directory)

Deprecated. Imports from lib/inteceptor/ still work via barrel re-export but the implementation has moved to lib/interceptor/.

CacheStrategy.LEGACY

Deprecated. The legacy strategy using @nestjs/cache-manager is maintained for backward compatibility but will be removed in a future major release. Migrate to MEMORY, DISTRIBUTED, or MULTI_TIER.


API Reference

CacheManager

| Method | Description | |--------|-------------| | get<T>(keys, resolver?, ttlOrOptions?, options?) | Get or resolve+cache a value | | getOnly<T>(keys) | Get without resolver | | set<T>(keys, value, ttl?, options?) | Set a value with optional tags | | mset<T>(list) | Set multiple values | | mget(keys) | Get multiple values | | invalidate(key, options?) | Unified invalidation (key/pattern/tag modes) | | mInvalidate(list) | Batch invalidation with mixed modes | | reset() | Clear all cache entries | | touch(keys, ttl?) | Extend TTL of a cached item | | namespace(name) | Create a namespaced cache manager | | isHealthy() | Health check | | getStats() | Cache statistics (enhanced mode) | | isEnhancedMode() | Check if using cacheable | | resolveKey(keyParts) | Resolve key parts to string |

CacheModule

| Method | Description | |--------|-------------| | register(options) | Synchronous registration | | registerAsync(options) | Async registration with factory |

License

See the root monorepo license.