NANDHOO.

The Anatomy of an HTTP Request

The Anatomy of an HTTP Request


When your browser wants a web page, it sends an HTTP Request. But what's actually inside that request? It's not just "Give me the page!" — it's a carefully structured message that follows a precise set of rules defined in the HTTP specification.




1. The Four Parts of Every HTTP Request


┌─────────────────────────────────────────────────────────┐
│  REQUEST LINE                                           │
│  POST /api/users HTTP/1.1                               │
├─────────────────────────────────────────────────────────┤
│  HEADERS                                                │
│  Host: api.nandhoo.com                                 │
│  Content-Type: application/json                         │
│  Authorization: Bearer eyJhbGci...                      │
│  Accept: application/json                               │
│  User-Agent: Mozilla/5.0 (Macintosh; Intel...)          │
├─────────────────────────────────────────────────────────┤
│  BLANK LINE  (separates headers from body)              │
│                                                         │
├─────────────────────────────────────────────────────────┤
│  BODY  (optional — only for POST, PUT, PATCH)           │
│  {"username": "NANDHOO", "email": "hi@nandhoo.com"}   │
└─────────────────────────────────────────────────────────┘



2. The Request Line


The first line of every HTTP request contains three things:


METHOD  PATH  HTTP-VERSION
  │       │       │
  GET   /api/users  HTTP/1.1

Method

The verb that describes the action you want to perform. Common methods:


MethodPurposeHas Body?Idempotent?
GETRetrieve a resourceNoYes
POSTCreate a new resourceYesNo
PUTReplace an existing resource entirelyYesYes
PATCHUpdate part of a resourceYesNo
DELETERemove a resourceOptionalYes
HEADSame as GET but returns headers onlyNoYes
OPTIONSAsk which methods a server supportsNoYes

Idempotent means calling the method multiple times has the same effect as calling it once. DELETE /users/123 twice still results in the user being deleted — same end state.


Path

The specific resource you want to access on the server.


/api/users/42/posts?page=2&limit=10
│              │    │
│              │    └── Query string (extra parameters)
│              └──────── Resource identifier
└─────────────────────── Base path

HTTP Version


VersionYearKey feature
HTTP/1.01996New connection for every request
HTTP/1.11997Persistent connections (keep-alive), chunked transfer
HTTP/22015Multiplexing, header compression, server push
HTTP/32022Built on QUIC/UDP, even faster



3. The URL and its Components


A full URL has several distinct parts:


https://api.nandhoo.com:443/v1/users/42?tab=badges&sort=asc#section-1
  │           │           │   │           │                    │
  │           │           │   │           │                    └── Fragment (not sent to server)
  │           │           │   │           └────────────────────── Query string
  │           │           │   └────────────────────────────────── Path
  │           │           └────────────────────────────────────── Port (optional if default)
  │           └────────────────────────────────────────────────── Host (domain)
  └────────────────────────────────────────────────────────────── Scheme / Protocol

Query Strings


Query strings pass extra parameters to the server without changing the path:


/api/users?sort=desc&page=3&limit=20&search=alice

Key-value pairs: sort = desc page = 3 limit = 20 search= alice


In JavaScript, use URLSearchParams to safely build query strings:


const params = new URLSearchParams({
  sort: 'desc',
  page: '3',
  limit: '20',
  search: 'alice'
});

const url = https://api.nandhoo.com/users?${params}; // → https://api.nandhoo.com/users?sort=desc&page=3&limit=20&search=alice


fetch(url).then(r => r.json()).then(console.log);


URL Encoding


Special characters in URLs must be percent-encoded:


CharacterEncoded
Space%20 or +
&%26
=%3D
#%23
/%2F
@%40

encodeURIComponent() handles this for you:


const search = 'coffee & cake';
const safe   = encodeURIComponent(search); // 'coffee%20%26%20cake'



4. Request Headers — The Metadata Envelope


Headers give the server extra context. Each is a Key: Value pair on its own line.


Common Request Headers


HeaderExamplePurpose
Hostapi.nandhoo.comWhich server to talk to (required in HTTP/1.1+)
User-AgentMozilla/5.0 (Mac)Browser and OS info
Acceptapplication/jsonWhat response format the client wants
Accept-Languageen-GB,en;q=0.9Preferred language(s)
Accept-Encodinggzip, deflate, brCompression algorithms the client supports
Content-Typeapplication/jsonFormat of the request body
Content-Length47Size of the request body in bytes
AuthorizationBearer eyJhbGci...Authentication token or credentials
CookiesessionId=abc123Cookies to send with the request
Refererhttps://google.comURL of the previous page
Originhttps://nandhoo.comWhere the request originated (CORS)
Cache-Controlno-cacheCaching directives
Connectionkeep-aliveKeep the TCP connection open for reuse

The Accept Header and Content Negotiation


The Accept header lets the client tell the server what format it can handle. The server picks the best match.


Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Meaning: text/html → quality 1.0 (most preferred) application/xhtml+xml → quality 1.0 application/xml → quality 0.9 / → quality 0.8 (accept anything as last resort)




5. The Request Body


The body carries the actual data for POST, PUT, and PATCH requests. GET and DELETE requests rarely have a body.


Body Format 1: JSON (Most Common for APIs)


fetch('https://api.nandhoo.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    username: 'NANDHOO',
    email:    'hi@nandhoo.com',
    level:    'beginner'
  })
});

Raw request on the wire:

POST /users HTTP/1.1
Host: api.nandhoo.com
Content-Type: application/json
Content-Length: 62

{"username":"NANDHOO","email":"hi@nandhoo.com","level":"beginner"}


Body Format 2: Form Data (HTML Form Submissions)


<form method="POST" action="/login">
  <input name="email"    type="email">
  <input name="password" type="password">
  <button type="submit">Log in</button>
</form>

The browser sends:

POST /login HTTP/1.1
Host: www.nandhoo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 41

email=hi%40nandhoo.com&password=secret123


Body Format 3: Multipart Form Data (File Uploads)


<form method="POST" action="/upload" enctype="multipart/form-data">
  <input name="avatar" type="file">
  <button type="submit">Upload</button>
</form>

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123

------WebKitFormBoundaryABC123 Content-Disposition: form-data; name="avatar"; filename="photo.jpg" Content-Type: image/jpeg


[binary JPEG data here...] ------WebKitFormBoundaryABC123--


Body Format 4: Plain Text


fetch('/api/notes', {
  method: 'POST',
  headers: { 'Content-Type': 'text/plain' },
  body: 'This is my plain text note.'
});



6. A Complete Real HTTP Request


Here is an actual raw request as it travels across the network:


GET /api/v1/courses?level=beginner HTTP/1.1
Host: api.nandhoo.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
Accept: application/json
Accept-Language: en-GB,en;q=0.9
Accept-Encoding: gzip, deflate, br
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiJ9.X5k
Connection: keep-alive

Notice there's no body — this is a GET request.




7. CORS — Cross-Origin Requests


When your JavaScript fetches a URL on a different origin (different domain, protocol, or port), the browser adds an Origin header and the server must respond with permission.


// Your page is on: https://nandhoo.com
// This request goes to a DIFFERENT origin:
fetch('https://api.otherdomain.com/data');
// Browser adds: Origin: https://nandhoo.com

The server must include Access-Control-Allow-Origin: https://nandhoo.com (or *) in its response, otherwise the browser blocks the response entirely.


Preflight Requests


For non-simple requests (e.g., PUT with a custom header), the browser sends a preflight request first:


OPTIONS /api/resource HTTP/1.1
Host: api.otherdomain.com
Origin: https://nandhoo.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization, Content-Type

If the server responds with appropriate Access-Control-Allow-* headers, the actual request proceeds.




8. Building Requests with fetch — Full Reference


const response = await fetch('https://api.nandhoo.com/courses', {
  method: 'POST',                          // HTTP method

headers: { // Request headers 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token, 'Accept': 'application/json', 'X-App-Version': '1.2.0' // Custom headers start with X- },


body: JSON.stringify({ // Request body (auto-serialised) title: 'Intro to HTTP', level: 'beginner', chapters: 10 }),


credentials: 'include', // Send cookies cross-origin mode: 'cors', // CORS mode cache: 'no-cache', // Don't use cached response signal: AbortSignal.timeout(5000) // Cancel after 5 seconds });




9. Practical Patterns


Pattern 1 — GET with Query Parameters


async function searchCourses(query, level = 'all', page = 1) {
  const params = new URLSearchParams({ q: query, level, page });
  const res = await fetch(`/api/courses?${params}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

const results = await searchCourses('javascript', 'beginner', 2);


Pattern 2 — POST with Error Handling


async function createUser(userData) {
  const res = await fetch('/api/users', {
    method:  'POST',
    headers: { 'Content-Type': 'application/json' },
    body:    JSON.stringify(userData)
  });

if (res.status === 409) throw new Error('Username already taken'); if (res.status === 422) { const errors = await res.json(); throw new Error('Validation failed: ' + JSON.stringify(errors)); } if (!res.ok) throw new Error(Unexpected error: ${res.status});


return res.json(); // 201 Created with new user object }


Pattern 3 — File Upload with Progress


async function uploadAvatar(file) {
  const formData = new FormData();
  formData.append('avatar', file);
  formData.append('description', 'Profile photo');

// Note: DO NOT set Content-Type header manually — // the browser adds the correct multipart boundary automatically const res = await fetch('/api/upload/avatar', { method: 'POST', body: formData });


return res.json(); }




10. Common Mistakes


MistakeWhat goes wrongFix
Forgetting Content-Type: application/json on a POSTServer receives garbled data or ignores the bodyAlways set Content-Type when sending JSON
Setting Content-Type: multipart/form-data manuallyBrowser can't add the required boundary parameterNever set Content-Type for FormData uploads — let the browser do it
Putting sensitive data in the URL query stringURLs appear in server logs, browser history, and the Referer headerUse the request body or Authorization header for secrets
Not handling non-2xx responses.json() on an error response crashes or shows confusing dataAlways check response.ok or response.status before parsing
Building query strings with string concatenationSpecial characters break the URLUse URLSearchParams



11. Review Questions


  1. What are the four parts of an HTTP request?
  2. What does Content-Type: application/json tell the server?
  3. What does an Authorization: Bearer header contain?
  4. When should you use a request body, and for which HTTP methods?
  5. Why should you never put a password in a query string?
  6. What is URL encoding and why is it needed?
  7. What happens in a CORS preflight OPTIONS request?