javascript 59 lines · 10 steps

Building an in-memory cache middleware in Express

A configurable middleware factory that caches GET responses in memory and serves ETag-based 304s.

Explained by highlit
1const crypto = require('crypto');
2 
3function inMemoryCache({ maxAge = 60_000, maxEntries = 500 } = {}) {
4 const store = new Map();
5 
6 const evictExpired = () => {
7 const now = Date.now();
8 for (const [key, entry] of store) {
9 if (entry.expiresAt <= now) store.delete(key);
10 }
11 };
12 
13 return function cache(req, res, next) {
14 if (req.method !== 'GET') return next();
15 
16 const key = req.originalUrl;
17 const cached = store.get(key);
18 
19 if (cached && cached.expiresAt > Date.now()) {
20 res.set('ETag', cached.etag);
21 res.set('X-Cache', 'HIT');
22 if (req.headers['if-none-match'] === cached.etag) {
23 return res.status(304).end();
24 }
25 res.set(cached.headers);
26 return res.status(cached.status).send(cached.body);
27 }
28 
29 const originalSend = res.send.bind(res);
30 res.send = (body) => {
31 if (res.statusCode >= 200 && res.statusCode < 300) {
32 const payload = Buffer.isBuffer(body) ? body : Buffer.from(String(body));
33 const etag = '"' + crypto.createHash('sha1').update(payload).digest('hex') + '"';
34 res.set('ETag', etag);
35 res.set('X-Cache', 'MISS');
36 
37 if (store.size >= maxEntries) evictExpired();
38 if (store.size < maxEntries) {
39 store.set(key, {
40 etag,
41 status: res.statusCode,
42 headers: { 'Content-Type': res.get('Content-Type') },
43 body: payload,
44 expiresAt: Date.now() + maxAge,
45 });
46 }
47 
48 if (req.headers['if-none-match'] === etag) {
49 return res.status(304).end();
50 }
51 }
52 return originalSend(body);
53 };
54 
55 next();
56 };
57}
58 
59module.exports = inMemoryCache;
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A middleware factory closes over shared state like a Map so every request reuses one cache instance.
  2. 2Wrapping res.send lets you intercept and store a response body without changing route handlers.
  3. 3ETags plus if-none-match let the server return an empty 304 instead of re-sending unchanged bodies.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building an in-memory cache middleware in Express — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code