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: how long is this fresh?
Section titled “Cache-Control: how long is this fresh?”Cache-Control tells caches whether and how long a response may be reused:
Cache-Control: public, max-age=60 # any cache may reuse for 60sCache-Control: private, max-age=0 # only the client, must revalidateCache-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.
ETag + If-None-Match: did it change?
Section titled “ETag + If-None-Match: did it change?”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)
Last-Modified + If-Modified-Since is the timestamp-based equivalent. ETags are more precise (they catch any change, not just second-level time changes).
Computing an ETag
Section titled “Computing an ETag”Common mistakes
Section titled “Common mistakes”Caching sensitive data by accident Symptom:
Cache-Control: max-age=3600on/users/\{id\}/profile- A browser or CDN caches user A’s profile and serves it to user B
- Set
Cache-Control: no-store, privatefor 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-Matchbut 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
304on an authenticated request doesn’t count against the rate limitAWS S3:
- The ETag is the MD5 hash of the object content (for non-multipart, non-SSE-KMS uploads)
- Conditional GET via
If-None-Matchmakes syncing large numbers of files efficientSources: GitHub REST API — best practices, AWS S3 Object metadata (ETag)