Command Palette

Search for a command to run...

Hectal
PHASE 11Intermediate ~26 min· topic 1 of 8

Topic 11.1

HTTP for JavaScript Developers

In one line

HTTP is the request-and-reply conversation between the browser and a server: every request has a method, a URL and headers, every reply has a status code, headers and a body, and the browser's CORS rules decide which replies your JavaScript may read.

Think of it like this

Ordering at a restaurant counter. You hand over an order slip (the request): what you want to do ("order", "cancel"), which item, and a few notes ("no onions", "I'm paying by card"). The kitchen hands back a tray with a ticket on it (the response): the ticket says how it went ("here you go", "sold out", "kitchen on fire"), a few notes, and then the food itself. HTTP is exactly this slip-and-tray conversation between your browser and a server.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Client and server
The client (your browser) asks for things; the server (a computer on the internet) answers.
Request
What the client sends: a method, a URL, headers and sometimes a body.
Response
What the server sends back: a status code, headers and usually a body.
Method
The verb of a request: GET to read, POST to create or send, PUT/PATCH to change, DELETE to remove.
Status code
A three-digit number in the response that says how it went, like 200 (fine) or 404 (not found).
Header
A name: value note attached to a request or response, like Content-Type: application/json.
JSON
A text format for data that looks like JavaScript objects and arrays. Almost every web API uses it.
Origin
The scheme, host and port of a URL together, like https://hectal.in. Two URLs with any part different are different origins.
CORS
Cross-Origin Resource Sharing: headers a server sends to let pages from other origins read its replies.
Idempotent
Doing it twice has the same effect as doing it once. GET, PUT and DELETE are idempotent; POST usually isn't.

Step by step

01Look at a real request

Open DevTools (F12), go to the Network tab and reload any page. Every row is one request. Click a row: the Headers panel shows the method, URL, status code and both sets of headers; Response shows the body.

Written out as plain text, a small HTTP/1.1 request and its reply look like this. (HTTP/2 and HTTP/3 send the same information in a compact binary form, but DevTools shows it the same way.)

request and responsewhole filehttp
GET /api/users/42 HTTP/1.1
Host: api.example.com
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=60

{"id":42,"name":"Asha"}

02One request, one response

The client always speaks first, and each request gets exactly one response. The server never "pushes" a plain HTTP response by itself; for that there are other tools (WebSockets, server-sent events) built on top.

One request, one responsediagram
Rendering diagram…

03Read the status code first

Your code should decide what to do from the number: 2xx means use the body, 401 means log in again, 404 means show "not found", 429 means slow down and retry later, 5xx means the server is having trouble (a retry may help).

A 4xx or 5xx reply is still a reply: the server answered. That difference matters a lot in the next topic, where fetch treats a 500 as a successful conversation with a sad answer.

04Send and receive JSON

To send data, turn it into JSON text and say so with a header. To receive it, read the body as text and parse it (fetch's response.json() does both steps for you).

json.jswhole filejavascript
const user = { name: "Asha", joined: new Date("2024-01-15T10:30:00Z"), nickname: undefined };
const text = JSON.stringify(user);
console.log(text); // {"name":"Asha","joined":"2024-01-15T10:30:00.000Z"}
const back = JSON.parse(text);
console.log(typeof back.joined); // string, not a Date

05Cross-origin requests and the preflight

When https://hectal.in calls https://api.other.com with JSON, the browser first sends an OPTIONS request listing the method and headers it wants to use. Only if the server answers with matching Access-Control-Allow-* headers does the real request go out, and only if the real reply also allows the origin can your code read it.

Cross-origin requests and the preflightdiagram
Rendering diagram…

06When CORS says no

If the server doesn't send the right headers, the request may still have reached the server, but the browser hides the reply from your code. You see a red CORS message in the console and fetch rejects with TypeError: Failed to fetch. Your JavaScript can't read why, on purpose: that's what stops a random site from reading your bank's pages with your cookies.

The fix is always on the server (or a proxy on your own origin that calls the API for you). mode: "no-cors" doesn't help: it gives you an opaque response whose body you can't read.

Try it yourself

  1. 1

    Add a route to the fake server

    In the first example, add a branch for PATCH /api/users/1 that replies 200 OK with { id: 1, name: "Asha K" }, then call show("PATCH", "/api/users/1"). Predict the line before you press Run.

  2. 2

    Find the edges of ok

    In the first example's console, ok was true for 200, 201 and 204. Predict ok for 299, 300 and 199, then check: console.log(new Response(null, { status: 299 }).ok). (Hint: the constructor only allows 200 to 599, so 199 throws a RangeError.)

  3. 3

    Watch a real preflight

    On any site that calls an API on another domain, open DevTools → Network, tick Preserve log, and filter by Fetch/XHR. Look for an OPTIONS row right before a POST or PUT to the same URL, and read its Access-Control-* response headers.

Code & diagrams

A tiny fake server that returns real Response objects ES2017+ New tab

`Response` is the browser's real response class. `ok` is `true` for any status from 200 to 299. A 204 reply has no body, so there is nothing to parse.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

GET /api/users/1 -> 200 OK (ok: true)
  body: { id: 1, name: 'Asha' }
POST /api/users -> 201 Created (ok: true)
  body: { id: 2 }
DELETE /api/users/2 -> 204 No Content (ok: true)
GET /api/users/999 -> 404 Not Found (ok: false)
  body: { error: 'Not found' }
Status codes grouped by their first digit New tab

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

200 success: use the body
201 success: use the body
204 success: use the body
304 redirect or cached: the browser usually handles it
400 client error: fix the request, don't retry as-is
401 client error: log in again
404 client error: fix the request, don't retry as-is
429 client error: too many requests, wait and retry
500 server error: a retry later may work
503 server error: a retry later may work
Headers are case-insensitive, JSON is lossy New tab

The `Headers` object lowercases names. In JSON, the date became a string, `note` vanished, and the `Set` became an empty object.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

application/json
application/json
true
[ [ 'content-type', 'application/json' ], [ 'x-request-id', 'abc123' ] ]
{"item":"Tea","placedAt":"2024-01-15T10:30:00.000Z","tags":{}}
string false {}
What a CORS failure looks like (real network, not live)javascript
// Page on https://hectal.in calling a server that sends no CORS headers
try {
  const res = await fetch("https://api.other-site.example/data");
} catch (err) {
  console.log(err.name, err.message); // TypeError Failed to fetch
}
// The console also shows (from the browser, not from your code):
// Access to fetch at 'https://api.other-site.example/data' from origin 'https://hectal.in'
// has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
// on the requested resource.

// The fix lives on the server. Example reply headers that allow it:
// Access-Control-Allow-Origin: https://hectal.in
// Access-Control-Allow-Methods: GET, POST, PUT
// Access-Control-Allow-Headers: Content-Type
// Vary: Origin

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Parse an HTML error page as JSON

In the first example, make the 404 reply send "<!DOCTYPE html><html><body>Not found</body></html>" as its body instead of the JSON, and keep await res.json().

terminal
$ Console
── what you'll see ──
Uncaught (in promise) SyntaxError: Failed to execute 'json' on 'Response': Unexpected token '<', "<!DOCTYPE "... is not valid JSON

Break #2

Try to fix CORS from the front end

Add headers: { "Access-Control-Allow-Origin": "*" } to your own fetch call to an API on another origin that doesn't allow you.

terminal
$ Console
── what you'll see ──
Access to fetch at '...' from origin 'https://hectal.in' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Myth vs fact

Myth

A 404 or 500 means the request failed to reach the server.

Fact

Any status code means the server received the request and answered. A network failure (offline, DNS, CORS block) produces no status at all.

Myth

CORS protects the server from other websites.

Fact

CORS protects users: it stops a page on one origin reading responses from another with the user's cookies. The server still receives the request; tools like curl ignore CORS entirely. Real server protection is authentication and CSRF defences.

Myth

POST is more secure than GET.

Fact

Both travel inside the same HTTPS encryption. The difference is that GET parameters end up in the URL, which lands in history, logs and Referer headers, so sensitive data belongs in a body, not a query string.

Myth

JSON is just JavaScript.

Fact

JSON is a stricter text format: keys must be double-quoted, no trailing commas, no comments, no undefined, functions or dates. JSON.parse throws on anything else.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A request counts as simple (no preflight) only with method GET, HEAD or POST, no custom headers, and a Content-Type of text/plain, multipart/form-data or application/x-www-form-urlencoded. application/json always preflights. Servers can cache the preflight answer with Access-Control-Max-Age to save a round trip.

  • ▸

    Cookies are only sent cross-origin when the request uses credentials: "include" and the server replies with Access-Control-Allow-Credentials: true and a specific origin (not *). Cookies also need SameSite=None; Secure to travel on cross-site requests at all.

  • ▸

    A server that varies Access-Control-Allow-Origin by the request's Origin must also send Vary: Origin, or a shared cache (CDN) may serve one origin's permission to another. See /system-design for caching and CDNs.

  • ▸

    HTTP/2 and HTTP/3 multiplex many requests over one connection, so the old trick of bundling everything into one request matters less than it did with HTTP/1.1, where browsers opened only about six connections per host.

Remember this

  1. 1

    HTTP (HyperText Transfer Protocol) is the set of rules browsers and servers use to talk. The browser (the client) sends a request; the server (a computer somewhere that holds the data) sends back one response. Every web page, image, script and data call travels this way. HTTPS is the same protocol inside an encrypted connection (TLS), so nobody in between can read or change it. Modern sites use HTTPS for everything, and many browser APIs only work on HTTPS pages.

  2. 2

    A request has four parts. The method says what you want to do: GET reads something, POST creates something or sends data to be processed, PUT replaces something, PATCH changes part of something, DELETE removes something. The URL says which thing (https://api.example.com/users/42). Headers are name: value notes about the request (Accept: application/json means "please answer in JSON"). The body carries data, usually only with POST, PUT and PATCH. GET is meant to be safe (it changes nothing), and GET, PUT and DELETE are idempotent: sending the same request twice has the same effect as sending it once, which is why browsers and libraries may retry them.

  3. 3

    A response starts with a status code, a three-digit number grouped by its first digit. 2xx means success (200 OK, 201 Created, 204 No Content, which has no body). 3xx means "look elsewhere" (301 Moved Permanently, 304 Not Modified, which tells the browser its cached copy is still good). 4xx means the client made a mistake (400 Bad Request, 401 Unauthorized meaning "who are you?", 403 Forbidden meaning "I know who you are, and no", 404 Not Found, 409 Conflict, 422 Unprocessable Content, 429 Too Many Requests). 5xx means the server failed (500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable). The words after the number are the status text; code should check the number, not the text.

  4. 4

    Response headers describe the body and how to treat it: Content-Type: application/json says the body is JSON, Cache-Control: max-age=60 says it may be reused for 60 seconds, Set-Cookie stores a cookie. Header names are case-insensitive: Content-Type and content-type are the same header. In JavaScript, the Headers object handles that for you and always gives names back in lowercase.

  5. 5

    Most APIs (Application Programming Interfaces: URLs built for programs rather than people) send JSON (JavaScript Object Notation): text that looks like a JavaScript object, with double-quoted keys and only strings, numbers, booleans, null, arrays and objects. JSON.stringify(value) turns a value into JSON text to send; JSON.parse(text) turns received text back into a value. Dates, undefined, functions, Map and Set don't survive the trip: a date becomes a string, and undefined properties disappear.

  6. 6

    The browser enforces the same-origin policy. An origin is the scheme, host and port together: https://hectal.in and https://api.hectal.in are different origins, and so are http:// and https:// versions of one site. JavaScript on one origin may send a request to another, but it may only read the reply if the server allows it with CORS (Cross-Origin Resource Sharing) headers such as Access-Control-Allow-Origin: https://hectal.in. For "non-simple" requests (a PUT, or a POST with Content-Type: application/json, or custom headers) the browser first sends an OPTIONS preflight request to ask permission. CORS is set on the server; nothing in your front-end code can switch it off.

Explain it without notes

01

What are the parts of an HTTP request and of an HTTP response?

02

Explain the status code families and give two examples of each.

03

What is the same-origin policy, and how does CORS relax it?

04

When does the browser send a preflight request, and why?

Practice

01

Write a function isRetryable(status) that returns true for 408, 429 and any 5xx status, and false otherwise. Log the result for 404, 429 and 503.

02

Build a Headers object with Accept: application/json and Authorization: Bearer abc, then log every header name, one per line.

Trade-offs

  • ↔

    JSON is readable and universal but larger and slower to parse than binary formats; for huge payloads, compression (gzip, Brotli) on the server usually matters more than switching format.

  • ↔

    Calling a third-party API straight from the browser is simple but needs CORS and exposes any key you send; calling it through your own server hides keys and avoids CORS, at the cost of running that server.

Done when you can

  • I can name the parts of a request and a response.

  • I can say what 200, 201, 204, 304, 400, 401, 403, 404, 429, 500 and 503 mean.

  • I know header names are case-insensitive and JSON loses dates, undefined and functions.

  • I can explain what an origin is and why CORS exists.

  • I can tell when a request needs a preflight.