
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
express-memorize
Advanced tools
In-memory cache middleware for Express.js. Caches GET responses with optional TTL — zero dependencies, fully typed.
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 | BYPASS 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 });
});
Use cache.deleteMatching(pattern) to remove all cache entries whose keys match a glob pattern. This is useful when you don't know the exact key — for example, when a URL may have different query strings.
// Cached keys: /api/users/abc123, /api/users/abc123?lang=es, /api/users/abc123?page=1
app.put('/users/:id', (req, res) => {
users.update(req.params.id, req.body);
// Remove all cached variants of this user, regardless of query params
const deleted = cache.deleteMatching(`**/users/${req.params.id}*`);
console.log(`${deleted} cache entries removed`);
res.json({ ok: true });
});
Glob rules:
| Pattern | Behaviour |
|---|---|
* | Matches any sequence of characters within a single path segment (does not cross /) |
** | Matches any sequence of characters across path segments (crosses /) |
? | Matches any single character except / |
deleteMatching returns the number of entries removed and emits a delete event for each one.
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
hits: number; // total times this key was requested
}
hits starts at 1 on the initial cache miss (when the entry is stored) and increments by 1 on every subsequent cache hit. It resets to 1 if the entry is evicted and re-cached.
// Example: monitoring hot keys
const entries = cache.getAll();
for (const [key, info] of Object.entries(entries)) {
console.log(`${key} → ${info.hits} hits`);
}
// /users → 42 hits
// /products → 7 hits
cache.delete('/users'); // remove one entry
cache.deleteMatching('**/users/*'); // remove all /users/* entries
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. |
deleteMatching | (pattern: string) => number | Removes all entries matching a glob pattern. Returns the count removed. |
clear | () => void | Removes all entries. |
| Event | Payload | When |
|---|---|---|
set | { type, key, body, statusCode, contentType, expiresAt } | A response is stored |
delete | { type, key } | cache.delete(), cache.deleteMatching(), 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 |
X-Cache | BYPASS | Cache skipped — noCache: true was set for this route |
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.FAQs
In-memory cache middleware for Express.js. Caches GET responses with optional TTL — zero dependencies, fully typed.
We found that express-memorize demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.