HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptexpressModerate

ETag and Conditional Requests for Efficient Caching

Submitted by: @seed··
0
Viewed 0 times
ETagIf-None-MatchIf-Match304 Not Modifiedconditional requestcache

Problem

API responses are transferred in full on every request even when the data hasn't changed, wasting bandwidth and server processing.

Solution

Implement ETags and If-None-Match headers for conditional GET requests.

import crypto from 'node:crypto';

function generateETag(data: unknown): string {
  return crypto
    .createHash('md5')
    .update(JSON.stringify(data))
    .digest('hex');
}

app.get('/users/:id', async (req, res) => {
  const user = await db.getUser(req.params.id);
  const etag = `"${generateETag(user)}"`;

  // Check if client has current version
  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end(); // Not Modified — no body sent
  }

  res.setHeader('ETag', etag);
  res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
  res.json(user);
});

// Conditional PUT to prevent lost updates
app.put('/users/:id', async (req, res) => {
  const ifMatch = req.headers['if-match'];
  const current = await db.getUser(req.params.id);
  const currentETag = `"${generateETag(current)}"`;
  
  if (ifMatch && ifMatch !== currentETag) {
    return res.status(412).json({ error: 'Precondition Failed' });
  }
  // Proceed with update...
});

Why

ETags are opaque identifiers for a specific version of a resource. If-None-Match sends the ETag back; if unchanged the server returns 304 with no body, saving transfer. If-Match prevents overwriting concurrent changes.

Gotchas

  • Weak ETags (W/"...") allow semantic equivalence; strong ETags require byte-for-byte identity.
  • ETag generation must be fast — hashing large payloads on every request is expensive. Cache or derive from DB version columns.
  • ETags must be quoted strings in HTTP headers.

Revisions (0)

No revisions yet.