const crypto = require('crypto');
function computeStrongEtag(body) {
const buf = Buffer.isBuffer(body) ? body : Buffer.from(String(body));
const digest = crypto.createHash('sha1').update(buf).digest('base64');
return '"' + digest + '"';
}
function etagCache() {
return function (req, res, next) {
if (req.method !== 'GET') {
return next();
}
const originalSend = res.send.bind(res);
res.send = function (body) {
if (res.statusCode < 200 || res.statusCode >= 300 || body == null) {
return originalSend(body);
}
const payload = typeof body === 'object' && !Buffer.isBuffer(body)
? JSON.stringify(body)
: body;
const etag = computeStrongEtag(payload);
res.setHeader('ETag', etag);
if (isEtagMatch(req.headers['if-none-match'], etag)) {
res.status(304);
res.removeHeader('Content-Type');
res.removeHeader('Content-Length');
return originalSend('');
}
return originalSend(payload);
};
next();
};
}
module.exports = { etagCache, computeStrongEtag };
function normalize(tag) {
return tag.trim().replace(/^W\//, '');
}
function isEtagMatch(ifNoneMatch, currentEtag) {
if (!ifNoneMatch) {
return false;
}
const header = ifNoneMatch.trim();
if (header === '*') {
return true;
}
const candidates = header
.split(',')
.map(normalize)
.filter(Boolean);
const current = normalize(currentEtag);
return candidates.indexOf(current) !== -1;
}
module.exports = { isEtagMatch };
const express = require('express');
const { etagCache } = require('./etagCache');
const ProductRepo = require('./ProductRepo');
const router = express.Router();
router.use(etagCache());
router.get('/products', async (req, res, next) => {
try {
const products = await ProductRepo.list({ limit: 50 });
res.set('Cache-Control', 'private, max-age=0, must-revalidate');
res.json(products);
} catch (err) {
next(err);
}
});
router.get('/products/:id', async (req, res, next) => {
try {
const product = await ProductRepo.find(req.params.id);
if (!product) {
return res.status(404).json({ error: 'not_found' });
}
res.set('Cache-Control', 'private, max-age=0, must-revalidate');
res.json(product);
} catch (err) {
next(err);
}
});
module.exports = router;
This snippet shows how conditional HTTP caching works in Express by generating strong ETag values for GET responses and short-circuiting unchanged responses with 304 Not Modified. The idea is that once a client has already downloaded a representation, it can send back the previously received validator and the server can confirm the cache is still fresh without resending the body — saving bandwidth and rendering work while keeping correctness.
The etagCache middleware wraps the response by monkey-patching res.send. It only acts on safe, cacheable GET responses with a 2xx status, computes a content hash over the outgoing payload, and sets that hash as the ETag header. The helper computeStrongEtag normalises the body to a Buffer and derives a base64 SHA-1 digest, deliberately producing a strong validator (no W/ prefix) so byte-for-byte equality is asserted. This is the key trade-off: strong validators are precise but require hashing the full body, whereas weak validators would tolerate semantically-equivalent changes at lower cost.
Comparison is delegated to isEtagMatch, which parses the client's If-None-Match header, supports the * wildcard, splits multiple candidate tags, and normalises weak/strong forms before comparing. When a match is found, res.status(304) is set and the body is dropped by sending an empty payload — a 304 must not include a message body. Crucially, the middleware clears Content-Type and Content-Length on a 304 and preserves Cache-Control, matching the semantics the HTTP spec expects.
The products route tab shows the middleware applied at the router level, so every JSON response automatically gains an ETag. Because the hash is computed from the serialized response, no per-route bookkeeping is required; identical data always yields the same validator, and any change invalidates it.
A pitfall worth noting: this design hashes after the body is built, so it saves network transfer but not the database or serialization work that produced the body. For expensive computations, an application would pair this with an upstream key/value cache or a Last-Modified short-circuit. The pattern is best when payloads are moderate, responses are frequently re-requested, and correctness of freshness matters more than avoiding server-side compute.
Related snips
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Rails.application.configure do
config.after_initialize do
Bullet.enable = true
Bullet.alert = false
Bullet.bullet_logger = true
Bullet.console = true
N+1 query detection with Bullet gem
json.array! @posts do |post|
json.cache! ['v1', post], expires_in: 1.hour do
json.id post.id
json.title post.title
json.excerpt post.excerpt
json.published_at post.published_at
Fragment caching for expensive JSON serialization
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
package deps
import (
"net"
"net/http"
"time"
HTTP client tuned for production: timeouts, transport, and connection reuse
Share this code
Here's the card — post it anywhere.