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

JS get.js
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 it yourself
Result

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:

JS status.js
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:

JS helper.js
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

JS post.js
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:

JS url.js
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"));
Output
https://api.example.com/search?q=coffee+%26+tea&page=2
coffee & tea

Parallel requests

JS parallel.js
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:

JS abort.js
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

StatusMeaning
200 OK, 201 Created, 204 No ContentSuccess (204 has no body — don't call json())
400 Bad Request, 422 UnprocessableThe data you sent was invalid
401 Unauthorized, 403 ForbiddenNot logged in / not allowed
404 Not FoundWrong URL or missing resource
429 Too Many RequestsRate limited — slow down
500, 502, 503Server-side problems — maybe retry later