cache-manager

JavaScriptTypeScriptNode.jsLibraryCacheMulti-storeUnified Interface

GitHub Overview

jaredwray/cacheable

a robust, scalable, and maintained set of caching packages

Stars1,891
Watchers10
Forks200
Created:April 6, 2013
Language:TypeScript
License:MIT License

Topics

cachecachingcaching-libraryhttpkey-valuekeyvnodejsredisrfc-72wrapper

Star History

jaredwray/cacheable Star History
Data as of: 10/22/2025, 09:55 AM

Library

cache-manager

Overview

cache-manager is a cache module for Node.js that provides a multi-store cache manager capable of managing multiple cache stores through a unified interface. With TypeScript support, it enables easy construction of hierarchical cache architectures.

Details

cache-manager is a Node.js cache library that provides easy wrapping of functions in cache, tiered caches, and a consistent interface through plugin architecture. Built with TypeScript and compatible with ESModules, with version 7.0.0 being the latest release. It provides a mechanism to refresh expiring cache keys in background and supports various storage backends for caching content like Redis, MongoDB, databases, and filesystems. The multi-cache functionality allows creating cache hierarchy wrappers such as level 1 (memory) and level 2 (Redis), with configurable non-blocking mode that returns the first (fastest) value found or operates without waiting for all stores to complete when using multiple stores. It has proven track record with adoption in NestJS and other frameworks, and is used by over 1404 projects in the npm registry.

Advantages and Disadvantages

Advantages

  • Unified Interface: Operate multiple cache stores with consistent API
  • Full TypeScript Support: Type safety and excellent developer experience
  • Multi-store Support: Build hierarchical cache architectures
  • Rich Driver Support: Wide support for Redis, MongoDB, filesystem, etc.
  • Framework Integration: Proven track record and support in NestJS and others
  • Non-blocking Mode: Achieve fast response times
  • Plugin Architecture: Ensure extensibility and flexibility

Disadvantages

  • Complexity: Excessive features for simple caching needs
  • Performance Overhead: Slight performance degradation due to abstraction layers
  • Configuration Complexity: Complex configuration management for multi-store setups
  • Dependencies: Additional packages required depending on drivers used
  • Learning Curve: Concept understanding required to leverage advanced features

Key Links

Code Examples

Basic In-Memory Cache

import { createCache } from 'cache-manager'

// Create in-memory store
const memoryCache = createCache({
  store: 'memory',
  max: 100,    // Maximum items
  ttl: 60000   // TTL (milliseconds)
})

// Set value
await memoryCache.set('key', 'value', 30000) // 30 second TTL

// Get value
const value = await memoryCache.get('key')
console.log(value) // 'value'

// Delete value
await memoryCache.del('key')

// Clear cache
await memoryCache.reset()

Multi-store Setup (Tiered Cache)

import { createCache } from 'cache-manager'
import { createKeyv } from 'cacheable'
import { createKeyv as createKeyvRedis } from '@keyv/redis'

// Multiple store configuration
const memoryStore = createKeyv() // L1: Memory
const redisStore = createKeyvRedis('redis://user:pass@localhost:6379') // L2: Redis

const cache = createCache({
  stores: [memoryStore, redisStore]
})

// Using tiered cache
await cache.set('user:123', { name: 'John', age: 30 })

// Returns first found value (usually from memory)
const user = await cache.get('user:123')
console.log(user)

Automatic Caching with wrap Function

import { createCache } from 'cache-manager'

const cache = createCache({
  store: 'memory',
  ttl: 60000
})

// Wrap database access function with cache
async function getUserFromDb(userId) {
  console.log('Fetching from database...')
  // Simulate actual DB processing
  await new Promise(resolve => setTimeout(resolve, 100))
  return { id: userId, name: `User${userId}`, email: `user${userId}@example.com` }
}

// Add caching functionality with wrap function
async function getUser(userId) {
  return await cache.wrap(`user:${userId}`, () => getUserFromDb(userId))
}

// First call fetches from DB, subsequent calls from cache
const user1 = await getUser('123') // DB access
const user2 = await getUser('123') // From cache

Redis Integration

import { createCache } from 'cache-manager'
import { createRedisStore } from 'cache-manager-redis-store'

const redisCache = createCache({
  store: createRedisStore({
    host: 'localhost',
    port: 6379,
    password: 'your-password',
    db: 0
  }),
  ttl: 600 // 10 minutes
})

// Using Redis cache
await redisCache.set('session:abc123', {
  userId: '456',
  loginTime: new Date().toISOString()
}, 3600000) // 1 hour

const session = await redisCache.get('session:abc123')

Type-safe Usage with TypeScript

import { createCache, Cache } from 'cache-manager'

interface User {
  id: string
  name: string
  email: string
}

interface CacheConfig {
  userCache: Cache
  sessionCache: Cache
}

class UserService {
  private caches: CacheConfig

  constructor() {
    this.caches = {
      userCache: createCache({
        store: 'memory',
        max: 1000,
        ttl: 300000 // 5 minutes
      }),
      sessionCache: createCache({
        store: 'memory',
        max: 10000,
        ttl: 1800000 // 30 minutes
      })
    }
  }

  async getUser(userId: string): Promise<User | null> {
    const cacheKey = `user:${userId}`
    
    // Try to get from cache
    let user = await this.caches.userCache.get<User>(cacheKey)
    
    if (!user) {
      // Fetch from DB
      user = await this.fetchUserFromDatabase(userId)
      if (user) {
        await this.caches.userCache.set(cacheKey, user)
      }
    }
    
    return user
  }

  private async fetchUserFromDatabase(userId: string): Promise<User | null> {
    // Database access implementation
    return {
      id: userId,
      name: `User ${userId}`,
      email: `user${userId}@example.com`
    }
  }
}

Multi-store Non-blocking Mode

import { createCache } from 'cache-manager'
import { createKeyv } from 'cacheable'
import { createKeyv as createKeyvRedis } from '@keyv/redis'

const memoryStore = createKeyv()
const redisStore = createKeyvRedis('redis://localhost:6379')

const cache = createCache({
  stores: [memoryStore, redisStore],
  nonBlocking: true // Non-blocking mode
})

// In non-blocking mode:
// set/mset - does not wait for all stores to complete
// get/mget - returns first found value immediately
// del/mdel - does not wait for all stores to complete

await cache.set('fast-key', 'fast-value') // Completes immediately
const value = await cache.get('fast-key')  // Gets from fastest store

Custom Store Implementation

class CustomMemoryStore {
  constructor() {
    this.cache = new Map()
    this.timers = new Map()
  }

  async get(key) {
    return this.cache.get(key)
  }

  async set(key, value, ttl) {
    this.cache.set(key, value)
    
    if (ttl) {
      // Clear existing timer
      if (this.timers.has(key)) {
        clearTimeout(this.timers.get(key))
      }
      
      // Set new timer
      const timer = setTimeout(() => {
        this.cache.delete(key)
        this.timers.delete(key)
      }, ttl)
      
      this.timers.set(key, timer)
    }
  }

  async del(key) {
    if (this.timers.has(key)) {
      clearTimeout(this.timers.get(key))
      this.timers.delete(key)
    }
    this.cache.delete(key)
  }

  async reset() {
    this.timers.forEach(timer => clearTimeout(timer))
    this.cache.clear()
    this.timers.clear()
  }
}

// Using custom store
const customCache = createCache({
  store: new CustomMemoryStore()
})