Fetch & HTTP Requests
The fetch function lets JavaScript talk to servers: load data from an API, submit a form, upload a file — all without reloading the page. It returns a promise, so it's used with async/await. This lesson covers GET and POST requests, error handling, and the details that trip people up.
A GET request
async function loadUser(id) { const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`); const user = await response.json(); console.log(user.name, user.email); } loadUser(1);
Two steps, two awaits: fetch resolves as soon as the response headers arrive, giving you a Response object; then response.json() reads the body and parses it. Other body readers include text(), blob() (for files and images) and formData().
Try it live
This demo calls a free test API. Pick a user ID and load their details:
Try user 999, which doesn't exist: the server answers with status 404 and the code reports it.
fetch doesn't reject on HTTP errors
This is the most important detail of fetch. Its promise only rejects for network failures (offline, DNS failure, blocked by CORS). A 404 or 500 response is still a successful fetch! You must check the status yourself:
const response = await fetch("/api/missing"); console.log(response.ok); // false (ok is true only for 200–299) console.log(response.status); // 404 console.log(response.statusText);
A reusable helper keeps that check in one place:
async function getJSON(url, options) { const response = await fetch(url, options); if (!response.ok) { throw new Error(`${response.status} ${response.statusText} for ${url}`); } return response.json(); }
POST: sending data
async function createPost(title, body) { const response = await fetch("https://jsonplaceholder.typicode.com/posts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title, body, userId: 1 }), }); if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); } const created = await createPost("Hello", "My first post"); console.log(created.id); // the server's new ID
The second argument configures the request: method (GET, POST, PUT, PATCH, DELETE), headers, and body. For JSON, stringify the body and set the Content-Type header. For form data or file uploads, pass a FormData object as the body and don't set Content-Type — the browser sets it correctly.
Query parameters
Build URLs with URL and URLSearchParams rather than gluing strings together, so special characters are encoded correctly:
const url = new URL("https://api.example.com/search"); url.searchParams.set("q", "coffee & tea"); url.searchParams.set("page", 2); console.log(url.toString()); console.log(url.searchParams.get("q"));
https://api.example.com/search?q=coffee+%26+tea&page=2 coffee & tea
Parallel requests
const [user, posts] = await Promise.all([ getJSON("https://jsonplaceholder.typicode.com/users/1"), getJSON("https://jsonplaceholder.typicode.com/posts?userId=1"), ]); console.log(user.name, posts.length);
Independent requests should run at the same time with Promise.all, not one after another (lesson 52).
Timeouts and cancelling
fetch has no built-in timeout, but an AbortSignal can cancel a request:
try { const res = await fetch("/api/slow-report", { signal: AbortSignal.timeout(5000) }); console.log(await res.json()); } catch (err) { if (err.name === "TimeoutError") console.log("Took too long"); else throw err; }
For user-driven cancellation — say, a new search replacing an old one — create an AbortController, pass controller.signal to fetch, and call controller.abort().
CORS
For security, browsers only let a page read responses from another origin if that server allows it, by sending Access-Control-Allow-Origin headers. This is called CORS (Cross-Origin Resource Sharing). If you see a "blocked by CORS policy" error, the fix is on the server (or a proxy you control), not in your fetch call. Public APIs designed for browsers, like the one above, already allow it.
Common response statuses
| Status | Meaning |
|---|---|
| 200 OK, 201 Created, 204 No Content | Success (204 has no body — don't call json()) |
| 400 Bad Request, 422 Unprocessable | The data you sent was invalid |
| 401 Unauthorized, 403 Forbidden | Not logged in / not allowed |
| 404 Not Found | Wrong URL or missing resource |
| 429 Too Many Requests | Rate limited — slow down |
| 500, 502, 503 | Server-side problems — maybe retry later |