Understanding HTTP Responses
After your browser sends a request, the server sends back an HTTP Response. The response tells the client whether the operation succeeded, provides metadata about the data, and carries the actual content in its body.
1. The Structure of an HTTP Response
┌────────────────────────────────────────────────────────────┐
│ STATUS LINE │
│ HTTP/1.1 200 OK │
├────────────────────────────────────────────────────────────┤
│ HEADERS │
│ Date: Mon, 31 Mar 2025 07:38:00 GMT │
│ Content-Type: application/json; charset=utf-8 │
│ Content-Length: 284 │
│ Cache-Control: public, max-age=300 │
│ ETag: "a1b2c3d4" │
│ X-Request-Id: 7e4c9f21-ab12-4d3e-bcd1-9e5f2c3a8d71 │
├────────────────────────────────────────────────────────────┤
│ BLANK LINE │
│ │
├────────────────────────────────────────────────────────────┤
│ BODY │
│ {"id":42,"username":"Nandhoo","level":"intermediate"} │
└────────────────────────────────────────────────────────────┘
2. The Status Line
The status line has three components:
HTTP/1.1 200 OK
│ │ │
│ │ └── Reason phrase (human-readable, informational only)
│ └─────── Status code (the machine-readable part that matters)
└─────────────── HTTP version
The status code is a 3-digit number grouped by its first digit:
| Range | Category | Meaning |
|---|
| 1xx | Informational | Request received, continuing |
| 2xx | Success | Request was understood and processed |
| 3xx | Redirection | Client needs to take further action |
| 4xx | Client Error | The request had a problem |
| 5xx | Server Error | The server failed |
3. Common Status Codes in Depth
2xx — Success
| Code | Name | When to use |
|---|
| 200 | OK | Standard success for GET, PUT, PATCH |
| 201 | Created | Something was created (after POST) — should include a Location header pointing to the new resource |
| 202 | Accepted | Request received and queued (async processing — the work isn't done yet) |
| 204 | No Content | Success but no body (after DELETE, or PATCH with no response body) |
| 206 | Partial Content | Streaming — part of a file (used with Range requests for video scrubbing) |
3xx — Redirection
| Code | Name | When to use |
|---|
| 301 | Moved Permanently | Resource moved forever; browser and search engines update their records |
| 302 | Found | Temporary redirect; browser follows it but keeps the original URL cached |
| 304 | Not Modified | Response is in the browser's cache; don't re-download it |
| 307 | Temporary Redirect | Same as 302 but the method must NOT change (a POST stays POST) |
| 308 | Permanent Redirect | Same as 301 but the method must NOT change |
4xx — Client Errors
| Code | Name | When to use |
|---|
| 400 | Bad Request | Malformed request, missing required body field |
| 401 | Unauthorized | No auth or invalid credentials (misleadingly named — really means "unauthenticated") |
| 403 | Forbidden | Authenticated but not authorised for this resource |
| 404 | Not Found | Resource doesn't exist at this URL |
| 405 | Method Not Allowed | The URL exists but doesn't support this HTTP method |
| 409 | Conflict | Duplicate resource (e.g. email already registered) |
| 422 | Unprocessable Entity | Request is valid JSON but fails validation (wrong field values) |
| 429 | Too Many Requests | Rate limit exceeded |
5xx — Server Errors
| Code | Name | When to use |
|---|
| 500 | Internal Server Error | Unhandled exception on the server |
| 502 | Bad Gateway | The upstream server the proxy was talking to failed |
| 503 | Service Unavailable | Server is overloaded or down for maintenance |
| 504 | Gateway Timeout | Upstream server didn't respond in time |
4. Response Headers in Depth
Content Headers
| Header | Example | Purpose |
|---|
Content-Type | application/json; charset=utf-8 | Format of the body |
Content-Length | 284 | Exact body size in bytes |
Content-Encoding | gzip | Compression applied to the body |
Content-Language | en-GB | Language of the content |
Caching Headers
| Header | Example | Purpose |
|---|
Cache-Control | public, max-age=86400 | How and for how long the response can be cached |
ETag | "a1b2c3" | A fingerprint of the resource — for conditional requests |
Last-Modified | Mon, 31 Mar 2025 00:00:00 GMT | When the resource was last changed |
Expires | Tue, 01 Apr 2025 00:00:00 GMT | Absolute expiry (older — prefer Cache-Control) |
Vary | Accept-Encoding | Which request headers the server considers when caching |
Cache-Control Directives Explained
Cache-Control: public, max-age=86400, stale-while-revalidate=3600
│ │ │ │
│ │ │ └── Serve stale content for 1hr while revalidating
│ │ └───────────────── Cache freshness: 24 hours
│ └─────────────────────────── Any cache (CDN, browser) can store it
└─────────────────────────────────────── (vs "private" = only the browser can cache it)
Cache-Control: no-store
↑ Don't cache this at all (for sensitive data like bank account pages)
Cache-Control: no-cache
↑ Cache it, but always check with the server before using it
Security Headers
| Header | Example | Purpose |
|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains | Force HTTPS for 1 year |
X-Content-Type-Options | nosniff | Prevent MIME type sniffing |
X-Frame-Options | DENY | Prevent clickjacking via iframes |
Content-Security-Policy | default-src 'self' | Controls which resources can load |
Referrer-Policy | strict-origin-when-cross-origin | Controls the Referer header |
CORS Response Headers
| Header | Example | Purpose |
|---|
Access-Control-Allow-Origin | https://nandhoo.com | Which origins can access the response |
Access-Control-Allow-Methods | GET, POST, PUT, DELETE | Which HTTP methods are allowed |
Access-Control-Allow-Headers | Authorization, Content-Type | Which request headers are allowed |
Access-Control-Max-Age | 86400 | How long to cache the preflight response |
Identity and Redirect Headers
| Header | Purpose |
|---|
Location | URL to redirect to (3xx) or URL of newly created resource (201) |
Set-Cookie | Instructs browser to store a cookie |
X-Request-Id | Unique ID for this specific request (for server-side debugging) |
Retry-After | When to retry after a 429 or 503 (seconds or date) |
5. The Response Body
The body contains the actual content. Its format is defined by Content-Type.
JSON Response (API)
{
"id": 42,
"username": "Nandhoo",
"email": "hi@nandhoo.com",
"courses": [
{ "id": 1, "title": "HTML5", "progress": 85 },
{ "id": 2, "title": "HTTP", "progress": 40 }
],
"createdAt": "2024-09-01T10:30:00Z"
}
HTML Response (Web Page)
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
<html lang="en">
<head><title>Nandhoo Dashboard</title></head>
<body>
<h1>Welcome back, Nandhoo!</h1>
</body>
</html>
Plain Text Response
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Server is healthy.
No Body Response (204)
HTTP/1.1 204 No Content
Date: Mon, 31 Mar 2025 07:38:00 GMT
[No body]
6. Conditional Requests and Caching in Action
ETags allow the client to ask "has this changed since last time?"
First request (cache empty):
Client → GET /api/courses/1
Server → 200 OK, ETag: "v2025-03-31", [body]
Second request (using cache):
Client → GET /api/courses/1, If-None-Match: "v2025-03-31"
Server → 304 Not Modified, [no body] ← no data downloaded!
After content changes:
Client → GET /api/courses/1, If-None-Match: "v2025-03-31"
Server → 200 OK, ETag: "v2025-04-01", [new body]
This saves significant bandwidth and speeds up apps that work with frequently-read but rarely-changed data.
7. Streaming Responses
For large or real-time data, the server can stream the response instead of sending it all at once.
const response = await fetch('https://api.nandhoo.com/large-dataset');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
received += value.length;
const chunk = decoder.decode(value, { stream: true });
console.log(`Received ${received} bytes so far...`);
process(chunk);
}
console.log('Stream complete!');
Streaming is used for:
- Video and audio players
- Large file downloads with progress bars
- Server-Sent Events for real-time data feeds
- AI chatbot token-by-token streaming
8. Reading Responses in JavaScript
async function callAPI(url) {
let response;
try {
response = await fetch(url, {
signal: AbortSignal.timeout(8000)
});
} catch (err) {
throw new Error('Network error: ' + err.message);
}
const contentType = response.headers.get('content-type') ?? '';
if (!response.ok) {
const body = contentType.includes('application/json')
? await response.json()
: await response.text();
throw new Error(`HTTP ${response.status}: ${JSON.stringify(body)}`);
}
if (contentType.includes('application/json')) return response.json();
if (contentType.includes('text/')) return response.text();
if (contentType.includes('image/')) return response.blob();
return response.arrayBuffer();
}
9. Inspecting Responses in DevTools
- Open Chrome DevTools → Network tab
- Click any request in the list
- Explore the sections:
| Tab | What you see |
|---|
| Headers | Status line, request headers, response headers |
| Preview | Rendered view of the response body (great for JSON) |
| Response | Raw response body text |
| Timing | Breakdown: DNS, TCP, TLS, TTFB (Time to First Byte), content download |
| Cookies | Any cookies set by this response |
TTFB (Time to First Byte) shows how long the server took to start sending data. A high TTFB means a slow server, not a slow network.
10. Visualising the Response Journey
Server processes request
│
▼
Compose status line: HTTP/1.1 200 OK
│
▼
Add response headers:
Content-Type: application/json
Cache-Control: max-age=300
ETag: "a1b2c3"
│
▼
Send blank line (headers ended)
│
▼
Send body: {"id":42,...}
│
▼ (arrives at browser)
Browser checks status code (200 → success)
Browser reads Content-Type → parses as JSON
Browser checks Cache-Control → stores in cache for 5 min
Browser checks ETag → saves for future conditional requests
11. Mini Exercises
- Open DevTools → Network tab and visit any website. Find a request with a 304 response. Why did the server send 304?
- Find a response that includes an ETag header. What does its value look like?
- Look at the Timing tab for any request. How long was the TTFB (Time to First Byte)?
- In the browser console, write
fetch code that reads the status code and Content-Type header before printing the body.
- Find a request that sets a cookie (look for
Set-Cookie in the response headers). What flags does it have (HttpOnly, Secure, SameSite)?
12. Key Terms
| Term | Meaning |
|---|
| Status code | 3-digit code summarising the result of the request |
| Response headers | Metadata describing how to process the body |
| Response body | The actual content returned (HTML, JSON, image, etc.) |
| ETag | A version fingerprint of a resource for caching |
| Cache-Control | Directives that control how the response is cached |
| 304 Not Modified | Tells the client to use its cached copy |
| TTFB | Time To First Byte — how long before the server starts sending data |
| Content-Type | The MIME type of the body (tells the client how to parse it) |
| CORS | Cross-Origin Resource Sharing — browser security mechanism |
| Set-Cookie | Instructs the browser to store a cookie |