Skip to main content

fetch in the Real World

intermediate16 min readLesson 75 of 143

Status handling, JSON bodies, headers, query params, pagination loops, and the loading/error UI contract.

fetch resolves on any response โ€” 404 and 500 included. Only network failure rejects. The first professional habit: check response.ok.

The shape every call should have

async function getUser(id) {
  const res = await fetch("/api/users/" + id);
  if (!res.ok) {
    throw new Error("HTTP " + res.status + " for user " + id);
  }
  return res.json(); // also a promise
}
  • res.ok is status in 200โ€“299.
  • res.json() parses the body; res.text() for anything else.
  • Request options: method, headers, body (JSON needs JSON.stringify plus Content-Type: application/json).
fetch("/api/tasks", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ title: "write tests" }),
});

Query params and pagination

Build queries with URLSearchParams โ€” it encodes for free:

const qs = new URLSearchParams({ page: String(page), perPage: "20", q: term });
const res = await fetch("/api/items?" + qs);

Paginated loops continue while a has-more signal exists (a next link, or page * perPage < total) โ€” never blindly until an empty page without a bound.

The UI contract

Every async view owes the user three states: loading (spinner/skeleton), error (message + retry affordance), data (or a polite empty state). Design the component around { status: "idle" | "loading" | "error" | "success", data, error } and rendering becomes trivial.

Now practice

fetch & Pagination โ€” PracticeAn API client that behaves like production: response checking, a paginated lister, and query building with URLSearchParams.3 challenges ยท ยท ~20 min