ETags and Conditional Requests

Express 24,430 puts an ETag on every body sent with res.send or res.json: a weak validator hashed from the body. When a client returns it as If-None-Match, Express compares the tags in its freshness check and answers 304 Not Modified with no body at all. The header semantics are Front-End Web Development material; what matters here is what the round trip costs and saves. Below, a 300-record route is fetched plainly, then with If-None-Match, then loaded both ways.

Output of 82
GET /books            -> 200, 27314 body bytes
  ETag: W/"6ab2-6hGo0QR9VBAafFXTNUlZbXuLS3I"
GET with If-None-Match -> 304, 0 body bytes
plain GET      4356 req/s  10.98 ms avg  120.0 MB/s  2xx=26130 3xx=0
If-None-Match  4515 req/s  10.56 ms avg    0.8 MB/s  2xx=0 3xx=22570

Read the last two lines carefully. Bandwidth fell from 120.0 to 0.8 MB per second — a factor of 150 — while throughput barely moved, from 4,356 to 4,515 requests per second. An ETag saves bytes, not CPU. The handler still ran, the list was still serialized to 27 KB and the hash was still computed; only the socket write was skipped. Conditional requests win for clients on a metered link and for your egress bill, and do nothing for a CPU-bound server — that needs the response cache of Server-Side Caching with Redis.

The W/ prefix marks the tag weak: two bodies with the same tag are semantically equivalent, not necessarily byte-identical. app.set('etag', 'strong') drops the prefix, app.set('etag', false) removes the header, and a function replaces the algorithm — worth it when you have a version number already and would rather not hash a large payload.

Cheaper validators than a hash of the bodyJavaScript
app.set('etag', (body, enc) => `"v${catalogVersion}"`);   // your own validator
app.get('/report', (req, res) => {
  res.set('Last-Modified', report.generatedAt.toUTCString());
  if (req.fresh) return res.status(304).end();            // also checks If-Modified-Since
  res.json(report);
});

Turning the hash off is measurable: the 20-record route of Measuring Performance went from 9,858 to 11,690 req/s, 19% faster, once app.set('etag', false) removed the digest from the hot path. Do that only where clients never revalidate. res.sendFile and express.static build tags from size and mtime instead (Cache Headers).