Redirects (Often Confused)
- 307 vs 308: both preserve method; 307 temporary, 308 permanent—use these for POST/PUT redirects
- 301/302 may change POST to GET (browser behavior)—don't use for API redirects with body
- Include
Locationheader with absolute URL—relative may fail in older clients - Redirect loops: limit to 5-10 follows; infinite loops crash clients
Caching Combinations
Cache-Control: no-storefor sensitive data—never written to diskno-cachestill caches but revalidates every time—not "don't cache"private, max-age=0, must-revalidatefor user-specific, always-fresh contentpublic, max-age=31536000, immutablefor versioned static assetsVary: Accept-Encoding, Authorizationwhen response depends on these headers—forgetting Vary breaks caching
Conditional Requests
ETag+If-None-Match: prefer for APIs—content hash based- Strong vs weak ETags:
"abc"vsW/"abc"—weak allows semantically equivalent responses If-Matchfor optimistic locking: fail update if resource changed since read- 412 Precondition Failed when
If-Matchfails—not 409 Conflict
CORS Preflight Triggers
- Custom headers (anything not Accept, Accept-Language, Content-Language, Content-Type simple values)
- Content-Type other than: application/x-www-form-urlencoded, multipart/form-data, text/plain
- PUT, DELETE, PATCH methods—even to same origin if other conditions met
- ReadableStream body—triggers preflight
- Preflight cached per
Access-Control-Max-Age—set to 86400 to reduce OPTIONS spam
Security Headers (Always Set)
Strict-Transport-Security: max-age=31536000; includeSubDomains—HSTS, once set can't easily undoX-Content-Type-Options: nosniff—prevents MIME sniffing attacksX-Frame-Options: DENYorSAMEORIGIN—prevents clickjackingContent-Security-Policy—complex but essential; start with report-only mode
Range Requests
Accept-Ranges: bytessignals support—clients can request partial contentRange: bytes=0-1023requests first 1024 bytes;bytes=-500requests last 500- Return 206 Partial Content with
Content-Range: bytes 0-1023/5000 - 416 Range Not Satisfiable if range invalid—include
Content-Range: bytes */5000
Error Response Best Practices
- Structured JSON errors:
{"error": {"code": "VALIDATION_FAILED", "message": "...", "details": [...]}} - Include request ID in error response—enables log correlation
- Don't leak stack traces in production—log server-side, return generic message
- 409 Conflict for business rule violations (duplicate email, insufficient funds)—not just 400
Retry Patterns
- Retry only idempotent methods by default—GET, PUT, DELETE, HEAD
- POST retry needs idempotency key—
Idempotency-Key: <client-generated-uuid> - Exponential backoff: 1s, 2s, 4s, 8s... with jitter—prevents thundering herd
- Respect
Retry-Afterheader—can be seconds or HTTP date - Set reasonable timeout (30s typical)—don't wait forever
Headers Often Forgotten
Vary: must include headers that affect response—CORS withoutVary: OriginbreaksContent-Disposition: attachment; filename="report.pdf"for downloadsX-Request-ID: generate if not present, propagate to downstream servicesAccept-Languagefor localized responses—respect with graceful fallback
Connection Behavior
- HTTP/1.1 without
Content-Lengthor chunked = connection close after response Transfer-Encoding: chunkedfor streaming—can't set Content-Length- HTTP/2 is binary, multiplexed—no head-of-line blocking at HTTP level
- WebSocket upgrade: GET with
Connection: Upgrade,Upgrade: websocket