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

@dtjldamien/nestjs-cache-service

v1.0.2

Published

NestJS cache service with write-locking to prevent thundering herd

Readme

nestjs-cache-service

CI npm version License: MIT

A NestJS cache service with write-locking to prevent the thundering herd problem.

Features

  • Write-locking: Prevents multiple concurrent requests from triggering duplicate expensive operations
  • Cache-aside pattern: Automatic cache population with getOrSet()
  • Multiple storage backends: Works with Redis, PostgreSQL, MySQL, MongoDB, SQLite, Memcached, and more via Keyv adapters
  • Multi-layer caching: Support for L1 (in-memory) and L2 (persistent) cache layers
  • TypeScript: Full type safety and IntelliSense support
  • Test coverage: Comprehensive test coverage

Installation

# Core dependencies
pnpm add @dtjldamien/nestjs-cache-service @nestjs/cache-manager cache-manager keyv

# Storage adapters (choose based on your needs)
pnpm add @keyv/redis      # For Redis
pnpm add @keyv/postgres   # For PostgreSQL
pnpm add @keyv/mysql      # For MySQL
pnpm add @keyv/mongo      # For MongoDB
pnpm add @keyv/sqlite     # For SQLite

Quick Start

1. Import the CacheModule

import { Module } from '@nestjs/common';
import { CacheModule } from '@dtjldamien/nestjs-cache-service';

@Module({
  imports: [
    CacheModule.registerAsync({
      useFactory: async () => ({
        // In-memory cache (default)
      }),
    }),
  ],
})
export class AppModule {}

2. Use CacheService in your services

import { Injectable } from '@nestjs/common';
import { CacheService } from '@dtjldamien/nestjs-cache-service';

@Injectable()
export class UserService {
  constructor(private cacheService: CacheService) {}

  async getUser(userId: string) {
    return this.cacheService.getOrSet(
      `user:${userId}`,
      async () => {
        // This expensive operation will only run once
        // even if multiple requests arrive simultaneously
        return this.fetchUserFromDatabase(userId);
      },
      3600000, // 1 hour TTL
    );
  }

  private async fetchUserFromDatabase(userId: string) {
    // Your database query here
  }
}

Configuration

Storage Options

This package uses Keyv for storage management, supporting multiple backends including Redis, PostgreSQL, MySQL, MongoDB, SQLite, and more. See the Keyv documentation for all available adapters.

Redis Configuration Examples

Basic Redis Setup

import { Module } from '@nestjs/common';
import { CacheModule } from '@dtjldamien/nestjs-cache-service';
import KeyvRedis from '@keyv/redis';

@Module({
  imports: [
    CacheModule.registerAsync({
      useFactory: async () => ({
        stores: [new KeyvRedis('redis://localhost:6379')],
        ttl: 3600000, // 1 hour
      }),
    }),
  ],
})
export class AppModule {}

Redis with Options

import { Module } from '@nestjs/common';
import { CacheModule } from '@dtjldamien/nestjs-cache-service';
import { Keyv } from 'keyv';
import KeyvRedis from '@keyv/redis';

@Module({
  imports: [
    CacheModule.registerAsync({
      useFactory: async () => ({
        stores: [
          new Keyv({
            store: new KeyvRedis({
              url: 'redis://localhost:6379',
              // Additional Redis options
              socket: {
                connectTimeout: 5000,
                keepAlive: true,
              },
            }),
            ttl: 3600000,
          }),
        ],
      }),
    }),
  ],
})
export class AppModule {}

Environment-based Configuration

import { Module } from '@nestjs/common';
import { CacheModule } from '@dtjldamien/nestjs-cache-service';
import { Keyv } from 'keyv';
import KeyvRedis from '@keyv/redis';

@Module({
  imports: [
    CacheModule.registerAsync({
      useFactory: async () => {
        // Use in-memory cache for testing
        if (process.env.NODE_ENV === 'test') {
          return {};
        }

        // Use Redis for production
        return {
          stores: [
            new Keyv({
              store: new KeyvRedis(`redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`),
            }),
          ],
        };
      },
    }),
  ],
})
export class AppModule {}

Multi-Layer Caching

Combine multiple stores for optimal performance with L1 in-memory cache and L2 persistent storage:

import { CacheModule } from '@dtjldamien/nestjs-cache-service';
import { Keyv } from 'keyv';
import KeyvRedis from '@keyv/redis';

@Module({
  imports: [
    CacheModule.registerAsync({
      useFactory: () => ({
        stores: [
          // L1: Fast in-memory cache (short TTL)
          new Keyv({ ttl: 60000 }), // 1 minute
          // L2: Redis for persistence (longer TTL)
          new Keyv({
            store: new KeyvRedis('redis://localhost:6379'),
            ttl: 3600000, // 1 hour
          }),
        ],
      }),
    }),
  ],
})
export class AppModule {}

With Dependency Injection

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CacheModule } from '@dtjldamien/nestjs-cache-service';
import { Keyv } from 'keyv';
import KeyvRedis from '@keyv/redis';

@Module({
  imports: [
    ConfigModule.forRoot(),
    CacheModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => {
        const redisHost = configService.get('REDIS_HOST');
        const redisPort = configService.get('REDIS_PORT');

        return {
          stores: [
            new Keyv({
              store: new KeyvRedis(`redis://${redisHost}:${redisPort}`),
            }),
          ],
          ttl: 3600000, // Default 1 hour
        };
      },
    }),
  ],
})
export class AppModule {}

API Reference

CacheService

get<T>(key: string): Promise<T | null>

Get a value from cache. If a write operation is in progress for this key, this method will wait for the write to complete before reading.

const value = await cacheService.get<UserData>('user:123');

set(key: string, value: any, ttl: number): Promise<void>

Set a value in cache with TTL (in milliseconds). This operation is protected by a write lock.

await cacheService.set('user:123', userData, 3600000); // 1 hour

del(key: string): Promise<void>

Delete a value from cache.

await cacheService.del('user:123');

clear(): Promise<void>

Clear all values from cache.

await cacheService.clear();

getOrSet<T>(key: string, factory: () => Promise<T>, ttl: number): Promise<T>

Cache-aside pattern: Get from cache or execute factory function and cache the result.

This method prevents the thundering herd problem. If multiple requests arrive for the same uncached key simultaneously, only one will execute the factory function while others wait.

const userData = await cacheService.getOrSet(
  `user:${userId}`,
  async () => {
    // This will only be called once, even with concurrent requests
    return await userRepository.findById(userId);
  },
  3600000, // 1 hour
);

Usage Patterns

Basic Caching

// Manual cache management
const cached = await cacheService.get<string>('my-key');
if (!cached) {
  const data = await expensiveOperation();
  await cacheService.set('my-key', data, 60000);
  return data;
}
return cached;

Cache-Aside Pattern (Recommended)

// Automatic cache management with thundering herd prevention
const data = await cacheService.getOrSet('my-key', async () => expensiveOperation(), 60000);

Cache Invalidation

// Delete specific key
await cacheService.del('user:123');

// Clear all cache
await cacheService.clear();

TTL Examples

// 1 minute
await cacheService.set('key', value, 60 * 1000);

// 1 hour
await cacheService.set('key', value, 60 * 60 * 1000);

// 24 hours
await cacheService.set('key', value, 24 * 60 * 60 * 1000);

Thundering Herd Prevention

The thundering herd problem occurs when multiple requests simultaneously attempt to regenerate the same expired cache entry, causing:

  • Multiple expensive operations (API calls, database queries)
  • Increased load on backend services
  • Slower response times

This package solves this by using write locks:

// Without write-locking (BAD):
// 10 simultaneous requests = 10 API calls

// With write-locking (GOOD):
// 10 simultaneous requests = 1 API call
const data = await cacheService.getOrSet(
  'expensive-key',
  async () => apiCall(), // Called only once
  3600000,
);

How It Works

  1. First request arrives for uncached key

    • Acquires write lock
    • Executes factory function
    • Caches result
    • Releases lock
  2. Concurrent requests for same uncached key

    • Wait for write lock
    • Check cache again after lock acquired
    • Return cached value (no factory execution)
  3. Subsequent requests for cached key

    • Return cached value immediately
    • No lock acquisition needed

Testing

# Run tests
pnpm test

# Run tests with coverage
pnpm test:cov

# Run tests in watch mode
pnpm test:watch

Building

# Build the package
pnpm build

# The output will be in the dist/ directory

Migration Guide

From Manual Cache Implementation

Before:

import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';

constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

async getData() {
  const cached = await this.cacheManager.get('key');
  if (cached) return cached;

  const data = await expensiveOp();
  await this.cacheManager.set('key', data, 60000);
  return data;
}

After:

import { CacheService } from '@dtjldamien/nestjs-cache-service';

constructor(private cacheService: CacheService) {}

async getData() {
  return this.cacheService.getOrSet(
    'key',
    async () => expensiveOp(),
    60000,
  );
}

From Internal CacheService

If you're migrating from an internal implementation:

  1. Update imports:
// Before
import { CacheService } from './shared/services/cache.service';

// After
import { CacheService } from '@dtjldamien/nestjs-cache-service';
  1. Update module imports:
// Before
import { CacheModule } from '@nestjs/cache-manager';

@Module({
  imports: [CacheModule.registerAsync({ ... })],
  providers: [CacheService],
  exports: [CacheService],
})

// After
import { CacheModule } from '@dtjldamien/nestjs-cache-service';

@Module({
  imports: [CacheModule.registerAsync({ ... })],
  // CacheService is now provided by CacheModule
})
  1. The API remains identical - no code changes needed in services using CacheService

License

MIT

Contributing

We welcome contributions to improve this package. Please follow these guidelines:

Development Setup

  1. Fork and clone the repository
  2. Install dependencies:
    pnpm install
  3. Create a feature branch:
    git checkout -b feature/your-feature-name

Code Standards

  • Follow the NestJS module best practices documented in AGENTS.md
  • Write TypeScript with strict type safety (no any types)
  • Maintain 100% test coverage for new features
  • Use meaningful commit messages following conventional commits format

Before Submitting

  1. Run tests: Ensure all tests pass

    pnpm test
  2. Check coverage: Maintain 100% coverage

    pnpm test --coverage
  3. Build successfully: Verify the build works

    pnpm build
  4. Lint and format: Code should pass linting

    pnpm lint

Pull Request Process

  1. Update the README.md with details of changes if needed
  2. Add tests for any new functionality
  3. Ensure the PR description clearly describes the problem and solution
  4. Reference any related issues in the PR description

Code Review Guidelines

  • Code must follow TypeScript best practices (see AGENTS.md)
  • All public APIs must have JSDoc comments
  • Breaking changes require a major version bump
  • New features should include usage examples

For detailed NestJS and TypeScript best practices, see AGENTS.md.

Support

For questions or issues, please open an issue in the repository or contact the maintainers.