node-cache
node-cache is a synchronous, process-local key/value cache for Node.js. It stores everything in one JavaScript object, can clone values on set and get, expires entries with per-key or default TTLs, emits lifecycle events, and exposes simple hit and miss statistics. It feels a little like Memcached at the method level, but there is no server, persistence, networking, replication, or sharing between Node processes. The package is useful for small disposable caches inside one long-running process, and its own README puts the practical ceiling at about one million keys.
Do not choose node-cache for a new distributed service: it is explicitly unmaintained and cannot share state between processes. It remains understandable and usable for a disposable cache in an older CommonJS application, but lru-cache is the safer default for new work.
Use it if
- You need a tiny synchronous cache inside one Node process and losing every entry on restart is acceptable
- You want per-key TTLs, expiry events, bulk get and set, and basic hit and miss counters without running a cache server
- You maintain CommonJS code and prefer require(), synchronous methods, and bundled TypeScript declarations
- You need cached values cloned by default so callers cannot accidentally mutate the stored copy
- You run multiple workers, containers, or servers: each process gets an isolated cache, so invalidation and values are not shared
- You need active maintenance: the README explicitly calls the project unmaintained, the latest npm release is 5.1.2 from 2020, and the last repository push was in 2024
- You might approach very large key counts: the README says all keys live in one object and gives a practical limit of about one million keys
- You cache Promises, functions, class instances, or other awkward mutable values: cloning is enabled by default and the README says some values such as Promises cannot be cloned
- You need bounded memory by bytes or least-recently-used eviction: maxKeys only limits the item count, TTL defaults to unlimited, and there is no LRU policy
Setup reality
Installation is only npm install node-cache, with no peer dependency, native build, credentials, service, or config file. Version 5.1.2 is CommonJS, so use require('node-cache') in CommonJS or a default interop import where your ESM and TypeScript setup supports it. The defaults deserve more attention than the install: stdTTL is 0, which means entries never expire; checkperiod is 600 seconds, so expired keys may remain allocated until accessed or swept; maxKeys is -1, so there is no entry cap; and useClones is true, so every set and get copies values. That cloning makes ordinary objects safer but costs CPU and can fail for values such as Promises. Setting useClones to false returns shared references, which is faster but allows callers to mutate cached state. A cache miss returns undefined, so storing undefined makes absence ambiguous. TTL arguments are seconds, while getTtl() returns an absolute millisecond timestamp, an easy unit mismatch. The cache is per process and disappears on restart, deploy, crash, worker recycle, or serverless cold start. With deleteOnExpire false, expired entries stay present and you are expected to handle the expired event yourself. Legacy callbacks must be explicitly enabled and were marked for removal in the never-released v6 rewrite. Call close() when lifecycle control matters, although the internal interval is unref'd and normally does not keep Node alive.
Patterns
Create a bounded cache with expiring entriescreate-cache
const NodeCache = require('node-cache');
const cache = new NodeCache({
stdTTL: 300,
checkperiod: 60,
maxKeys: 10000,
});stdTTL and checkperiod are seconds. The defaults are no TTL, a 600-second sweep, and no key limit.
Store and retrieve a valueset-and-get
cache.set('user:42', { name: 'Ada' }, 120);
const user = cache.get('user:42');
if (user === undefined) {
console.log('cache miss');
}A miss returns undefined, so do not store undefined if you need to distinguish a cached value from absence.
Use the cache-aside patterncache-aside
async function getUser(id) {
const key = `user:${id}`;
const cached = cache.get(key);
if (cached !== undefined) return cached;
const user = await db.users.findById(id);
cache.set(key, user, 60);
return user;
}Concurrent misses can all call the database because node-cache has no built-in request coalescing or async loader.
Store several entries at onceset-many
cache.mset([
{ key: 'feature:a', val: true, ttl: 30 },
{ key: 'feature:b', val: false, ttl: 30 },
{ key: 'feature:c', val: true },
]);Entries without ttl use stdTTL. mset returns true or throws when maxKeys would be exceeded.
Read several keysget-many
const values = cache.mget(['feature:a', 'feature:b']);
if (!Object.prototype.hasOwnProperty.call(values, 'feature:a')) {
console.log('feature:a missed');
}mget omits missing or expired keys and returns an object, not an array aligned to the requested keys.
Delete one or many entriesinvalidate-keys
const oneDeleted = cache.del('user:42');
const manyDeleted = cache.del(['user:43', 'user:44']);
console.log({ oneDeleted, manyDeleted });del returns the number of entries actually removed; deleting a missing key is not an error.
Read and remove a one-time valuetake-once
cache.set('otp:session-7', '493102', 180);
const code = cache.take('otp:session-7');
if (code === undefined) throw new Error('expired or already used');take is a synchronous get followed by deletion, but it is only atomic inside this one process and cannot coordinate multiple workers.
Inspect and extend an entry TTLinspect-ttl
const expiresAt = cache.getTtl('user:42');
if (expiresAt && expiresAt - Date.now() < 30_000) {
cache.ttl('user:42', 120);
}getTtl returns an absolute timestamp in milliseconds, 0 for no expiry, or undefined for a missing key; ttl() accepts seconds.
Observe expired entrieshandle-expiry
cache.on('expired', (key, value) => {
console.log('expired', key, value);
});Expiry listeners run in the same process. Keep handlers quick, and remember that process exit can happen before an entry expires.
Cache shared references for complex valuesavoid-cloning
const referenceCache = new NodeCache({
stdTTL: 30,
useClones: false,
});
referenceCache.set('client', databaseClient);With useClones false, callers receive the original reference and can mutate it. This is required for Promises and many class instances.
Inspect cache hit and miss statisticsread-stats
const { keys, hits, misses, ksize, vsize } = cache.getStats();
const requests = hits + misses;
const hitRate = requests ? hits / requests : 0;
console.log({ keys, hitRate, ksize, vsize });ksize and vsize are approximate byte counts, not enforceable memory limits. Use flushStats() to reset counters.
Clear data and stop the sweep intervalshutdown-cache
process.once('SIGTERM', () => {
cache.flushAll();
cache.close();
process.exit(0);
});flushAll removes all entries and close clears the checkperiod interval. Neither operation persists data for the next process.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| lru-cache | npm | Use it when you need actively maintained in-process caching with LRU eviction and size-based limits |
| quick-lru | npm | Use it for a small ESM-first LRU cache with a strict maximum entry count |
| cache-manager | npm | Use it when you want a higher-level cache API and may later add shared stores such as Redis |