Skip to content

Caching & ETags

HTTP has a powerful caching model built in. Using it well means a client (or a CDN) can avoid re-downloading data that has not changed — often the single biggest performance win an API gets.

Cache-Control tells caches whether and how long a response may be reused:

Cache-Control: public, max-age=60 # any cache may reuse for 60s
Cache-Control: private, max-age=0 # only the client, must revalidate
Cache-Control: no-store # never cache (sensitive data)

While a response is fresh, the client reuses it with no request at all. Once stale, it revalidates.

An ETag is a validator — a fingerprint of the current representation. On the next request the client sends it back in If-None-Match; if it still matches, the server returns 304 Not Modified with no body:

sequenceDiagram
  participant C as Client
  participant S as Server
  C->>S: GET /articles/42
  S-->>C: 200 OK + ETag: "v7"
  Note over C: later...
  C->>S: GET /articles/42 (If-None-Match: "v7")
  S-->>C: 304 Not Modified (no body)
A matching ETag turns a full response into a tiny 304

Last-Modified + If-Modified-Since is the timestamp-based equivalent. ETags are more precise (they catch any change, not just second-level time changes).

JavaScript

Caching sensitive data by accident Symptom:

  • Cache-Control: max-age=3600 on /users/\{id\}/profile
  • A browser or CDN caches user A’s profile and serves it to user B
  • Set Cache-Control: no-store, private for authenticated resources

An ETag that isn’t stable Symptom:

  • The ETag is generated from a timestamp — different servers generate different ETags for identical content
  • The client sends If-None-Match but the server returns 200 every time instead of 304
  • Generate the ETag from a hash of the content, not a timestamp

💡 Real-world examples

GitHub API:

  • Every resource carries an ETag — conditional GET avoids re-transferring unchanged data
  • A 304 on an authenticated request doesn’t count against the rate limit

AWS S3:

  • The ETag is the MD5 hash of the object content (for non-multipart, non-SSE-KMS uploads)
  • Conditional GET via If-None-Match makes syncing large numbers of files efficient

Sources: GitHub REST API — best practices, AWS S3 Object metadata (ETag)

What does a 304 Not Modified response contain?
Which header carries the validator a client sends back to revalidate?
Which Cache-Control directive means "never store this response"?