Migration Guide
This guide helps you migrate from popular Node.js caching libraries to Layercache. Each section includes before/after code examples, API mappings, and key differences.
Upgrading to 4.0
Layercache 4.0 resolves issue #90 by giving cache misses a JavaScript-native undefined result while preserving intentional null values.
Miss and null semantics
get(),getOrSet(),mget(),wrap(), and namespace reads returnundefinedon a miss or negative-cache hit.getOrThrow()throws only forundefined; a cachednullis returned normally.- Read-through fetchers cache
nullas a regular value by default. SetcacheNullValues: falseonly when your fetcher usesnullto mean absence. getEntry()keeps its metadata contract: it returnsnullwhen no entry exists and exposes negative entries withkind: 'empty'.- Custom
CacheLayerimplementations keep returningnullfrom their low-level read methods. Only the public stack and namespace APIs changed.
Replace public miss checks such as value === null with value === undefined. If your application previously used null as a fetcher miss, opt out explicitly:
Serializers retain their format-specific behavior: JSON omits undefined object properties and converts undefined array elements to null; MessagePack encodes undefined as nil and decodes it as null. Layercache does not store an undefined fetch result.
Security and operational boundaries
This release also deliberately changes cache and operator safety boundaries:
- Automatically derived structured
wrap()argument keys now use thej2:schema. Existingj:entries are left to expire and will be cold misses; no data migration is required. Plain objects with reserved native$typetags (Date,URL,RegExp,Map, orSet) now throw instead of colliding with native values. Use akeyResolverwhen those objects are intentional inputs. generationCleanup: truenow stops after discovering 10,000 unique old-generation keys in one cleanup run. SetgenerationCleanup: { batchSize, maxMatches }to choose a lower deployment-specific bound.maxMatches: falseis an explicit opt-out and should only be used when the keyspace is bounded elsewhere.- Write-through, write-behind, single-key, and
mset()writes share finite ordering state. The defaults admit 10,000 pending key-write units, 10,000 active keys, and 1,000 pending operations per key. TunewriteCoordinationfor known bursts and handleCacheWriteSaturationErroras backpressure rather than retrying without a limit.
Wildcard-only CLI invalidation patterns now all require --force, including combinations of * and ? such as ** and ?*.
Upgrading to 3.0
Layercache 3.0 is a major release because it changes operational defaults for Redis-backed deployments and HTTP middleware cache keys.
RedisTagIndex known-key shards
RedisTagIndex now defaults to 16 known-key shards. Existing deployments that used the previous single-set layout at <prefix>:keys are still read for compatibility, but you should migrate them before relying on the sharded layout in production:
Use knownKeysShards: 1 only when you intentionally need the legacy layout during a staged rollout.
Production Redis URLs in the CLI
CLI commands now reject plaintext redis:// URLs when NODE_ENV=production, unless --allow-plaintext is passed:
Implicit HTTP cache keys
Express and Hono middleware now bypass implicit URL-only caching when common sensitive query parameters are present. This avoids both storing secrets in cache keys and collapsing private responses into one scrubbed URL key. Provide a custom keyResolver when private responses are selected by query credentials.
The sensitive parameter names are access_token, api_key, apikey, auth, authorization, client_assertion, client_assertion_type, client_secret, code, credentials, id_token, jwt, password, private_key, refresh_token, secret, session, sessionid, session_id, and token. The same list is documented in the integration options.
DiskLayer protected entries
When DiskLayer is configured with encryptionKey or signingKey, plaintext legacy entries are now rejected by default. If you need to read old plaintext files during a migration, enable allowLegacyPlaintext: true temporarily and disable it after the cache directory has been rewritten.
OpenTelemetry key attributes
createOpenTelemetryPlugin() now exports layercache.key_hash by default. Pass { includeRawKeyAttributes: true } only if your telemetry backend is allowed to receive raw cache keys.
Generation persistence
Persist generation rotations when several instances share the same cache:
From node-cache-manager
Basic Setup
Before (node-cache-manager):
After (layercache):
Read-Through Fetch
Before:
After:
Set with TTL
Before:
After:
Delete
Before:
After:
Clear All
Before:
After:
API Mapping
Key Differences
TTL is in Milliseconds
node-cache-manager uses milliseconds:
Layercache uses milliseconds:
Auto Backfill
node-cache-manager requires manual warming:
Layercache auto-backfills L1:
Stampede Prevention
node-cache-manager requires plugins:
Layercache has built-in stampede prevention:
Tag Invalidation
node-cache-manager:
Layercache:
From keyv
Basic Setup
Before (keyv):
After (layercache):
Read-Through Fetch
Before:
After:
API Mapping
Key Differences
Multi-Layer is Native
keyv requires plugins:
Layercache has native multi-layer:
Read-Through Fetch
keyv requires manual checks:
Layercache has built-in read-through:
Namespaces
keyv:
Layercache:
From cacheable
Basic Setup
Before (cacheable):
After (layercache):
API Mapping
Key Differences
TTL Format
cacheable uses string durations:
Layercache uses numeric milliseconds:
Multi-Layer Orchestration
cacheable:
Layercache:
Distributed Consistency
cacheable:
Layercache:
Stampede Prevention
cacheable:
Layercache:
Operational Migration Tips
Replace Ad-Hoc Redis Key Scans
Before:
After:
Replace Manual Prefill Scripts
Before:
After:
Replace Custom Stats Endpoints
Before:
After:
Replace Manual Tag Tracking
Before:
After:
Replace Custom Invalidation Logic
Before:
After:
Replace Custom Locking for Stampede Prevention
Before:
After:
Migration Checklist
Phase 1: Setup
- Install Layercache:
npm install layercache@latest - Create CacheStack with equivalent layers
- Keep TTL values in milliseconds
- Configure distributed features (if needed)
Phase 2: Code Changes
- Replace
cache.wrap()withcache.get(key, fetcher) - Replace
cache.del()withcache.delete() - Replace
cache.reset()withcache.clear() - Update set operations to use options object:
{ ttl: 60_000 } - Add tags for group invalidation
Phase 3: Testing
- Verify cache hits/misses work correctly
- Test tag-based invalidation
- Test multi-layer cascading
- Test distributed coordination (if applicable)
- Monitor metrics and hit rates
Phase 4: Optimization
- Enable stampede prevention (default)
- Configure stale-while-revalidate
- Set up Prometheus metrics
- Configure health checks
- Set up distributed single-flight (if needed)
Common Migration Issues
TTL Confusion
Issue: Keeping old second-based TTL values after migrating.
Solution:
Missing Tags
Issue: Unable to invalidate related keys.
Solution:
Namespace Confusion
Issue: Not using namespaces for scoped caches.
Solution:
Not Using Read-Through
Issue: Still using manual miss handling.
Solution:
Need Help?
If you run into issues during migration:
- Check the API Reference for detailed method documentation
- Review examples for common patterns
- Open an issue with your current setup
We're happy to help you find the right approach for your use case!