how a read travels
100 concurrent requests. One database call.
read path · 1 of 4
L1 · Memory
~0.1 msAn in-process LRU answers hot keys without leaving the Node.js process. Most reads stop here and never go deeper.
read path · 2 of 4
L2 · Redis
~1 msShared across every instance. A miss takes a single-flight lease — one caller runs the fetcher while the rest wait for its result.
read path · 3 of 4
L3 · Disk
~5 msSurvives restarts and serves stale fallback when the layers above are cold or a fetch fails.
read path · 4 of 4
Origin · your database
50+ msThe layer you're protecting. Watch the stampede: every few seconds 14 concurrent requests fall — exactly one reaches the database.
then, on the way up
The result backfills every layer
One origin call refills disk, Redis, and memory on its way back — so the next read stops at L1 in a tenth of a millisecond.
capabilities
Production concerns, already handled.
Multi-layer stack
Memory, Redis, disk, and Memcached share one read-through interface with automatic backfill.
Read the docs →flightSingle-flight fetches
Concurrent callers collapse into one fetch locally, with Redis leases for distributed coordination.
Read the docs →tagsPrecise invalidation
Expire by tag, prefix, pattern, or namespace without throwing away stale fallback state.
Read the docs →opsOperational guardrails
Circuit breakers, timeout controls, Prometheus metrics, OpenTelemetry spans, and CLI inspection.
Read the docs →adaptersFramework integrations
Middleware and helpers for Express, Fastify, Hono, tRPC, GraphQL, and OpenTelemetry.
Read the docs →sandboxBrowser playground
Run real layercache examples in a worker-based sandbox, right inside this site.
Read the docs →quick start
Two layers in nine lines.
import { CacheStack, MemoryLayer, RedisLayer } from 'layercache'
import Redis from 'ioredis'
const cache = new CacheStack([
new MemoryLayer({ ttl: 60_000, maxSize: 1_000 }), // L1: in-process
new RedisLayer({ client: new Redis(), ttl: 3_600_000 }), // L2: shared
])
// Read-through: fetcher runs once, all layers filled
const user = await cache.get('user:123', () => db.findUser(123))