In-memory cache middleware for Express.js.
Caches GET responses with optional TTL — zero dependencies, fully typed.
GET responses automatically when status code is 2xxset, delete, expireX-Cache: HIT | MISS response headernpm install express-memorize
import express from 'express';
import { memorize } from 'express-memorize';
const app = express();
const cache = memorize({ ttl: 30_000 }); // 30 seconds global TTL
app.get('/users', cache(), async (req, res) => {
const users = await db.getUsers();
res.json({ data: users });
});
app.listen(3000);
The first request computes the response normally. Every subsequent GET /users is served from memory until the TTL expires.
Apply the cache to the entire application with app.use(). Every GET route is cached automatically — non-GET requests are bypassed without any extra configuration.
const cache = memorize({ ttl: 60_000 });
app.use(cache()); // applies to all GET routes
app.get('/users', (req, res) => { res.json({ data: users }) });
app.get('/products', (req, res) => { res.json({ data: products }) });
// POST, PUT, PATCH, DELETE routes are unaffected
const cache = memorize({ ttl: 60_000 });
app.get('/products', cache(), (req, res) => {
res.json({ data: products });
});
const cache = memorize({ ttl: 60_000 }); // global: 60s
app.get('/users', cache(), handler); // 60s
app.get('/products', cache({ ttl: 10_000 }), handler); // override: 10s
app.get('/config', cache({ ttl: 0 }), handler); // no expiry
const cache = memorize({ ttl: 30_000 });
app.get('/users', cache(), (req, res) => {
res.json({ data: users });
});
app.post('/users', (req, res) => {
users.push(req.body);
cache.delete('/users'); // invalidate after mutation
res.status(201).json({ data: req.body });
});
const cache = memorize({ ttl: 30_000 });
cache.on('set', (e) => {
console.log(`[cache] stored ${e.key} — expires in ${e.expiresAt ? e.expiresAt - Date.now() : '∞'}ms`);
});
cache.on('delete', (e) => {
console.log(`[cache] deleted ${e.key}`);
});
cache.on('expire', (e) => {
console.log(`[cache] expired ${e.key}`);
});
cache.get('/users'); // CacheInfo | null — single entry
cache.getAll(); // Record<string, CacheInfo> — all active entries
CacheInfo shape:
{
key: string;
body: unknown;
statusCode: number;
contentType: string;
expiresAt: number | null;
remainingTtl: number | null; // ms until expiry, null if no TTL
}
cache.delete('/users'); // remove one entry
cache.clear(); // remove all entries
memorize(options?)Creates a cache instance. Returns a Memorize object.
| Option | Type | Default | Description |
|---|---|---|---|
ttl |
number |
undefined |
Time-to-live in milliseconds. Omit for no expiry. |
cache(options?)Returns an Express RequestHandler middleware. Can override the global TTL.
| Option | Type | Default | Description |
|---|---|---|---|
ttl |
number |
global ttl |
TTL override for this specific route. |
| Method | Signature | Description |
|---|---|---|
get |
(key: string) => CacheInfo | null |
Returns info for a cached key. |
getAll |
() => Record<string, CacheInfo> |
Returns all active cache entries. |
delete |
(key: string) => boolean |
Removes a single entry. Returns false if not found. |
clear |
() => void |
Removes all entries. |
| Event | Payload | When |
|---|---|---|
set |
{ type, key, body, statusCode, contentType, expiresAt } |
A response is stored |
delete |
{ type, key } |
cache.delete() or cache.clear() is called |
expire |
{ type, key } |
TTL timer fires or lazy expiry is detected |
| Header | Value | Description |
|---|---|---|
X-Cache |
HIT |
Response served from cache |
X-Cache |
MISS |
Response computed and stored |
GET requests are cached. All other methods bypass the middleware entirely.2xx status code are stored.cache() returns an independent middleware handler, but all handlers created from the same memorize() instance share the same store.memorize() calls produce independent stores.