Home / Topic Library / JavaScript / Fetch API
⚡ JavaScript Topic Library

The Fetch API — Talking to Servers & APIs

fetch is the browser's built-in way to call APIs — every weather widget, login form and search box uses it. Its one dangerous quirk: a 404 does not throw an error unless you check for it.

⚡ Quick Answer

GET: const data = await (await fetch(url)).json() — but always check res.ok first, because fetch only rejects on network failure, not on 404/500. POST JSON: pass { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(obj) }.

§A correct GET request

get.js
async function getUser(id) {
  const res = await fetch(`https://api.example.com/users/${id}`);

  if (!res.ok) {                       // THE line everyone forgets
    throw new Error(`HTTP ${res.status} ${res.statusText}`);
  }
  return await res.json();             // parse JSON body
}

const user = await getUser(1);
console.log(user.name);

§POST — sending data

post.js
const res = await fetch("/api/scores", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ player: "Ada", score: 950 }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const saved = await res.json();

// other verbs work the same: method: "PUT" | "PATCH" | "DELETE"

§Complete error handling

errors.js
async function loadSafely(url) {
  try {
    const res = await fetch(url, {
      signal: AbortSignal.timeout(8000),   // give up after 8s
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    if (err.name === "TimeoutError") return { error: "Too slow" };
    if (err.name === "TypeError")    return { error: "Offline?" };
    return { error: err.message };        // HTTP errors from above
  }
}

§A tiny reusable helper

helper.js
async function api(url, options = {}) {
  const res = await fetch(url, {
    headers: { "Content-Type": "application/json", ...options.headers },
    ...options,
    body: options.body ? JSON.stringify(options.body) : undefined,
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.status === 204 ? null : res.json();
}

// usage:
const users = await api("/api/users");
await api("/api/users", { method: "POST", body: { name: "Ada" } });

§Common Mistakes to Avoid

🚨 Watch out for these
  • Not checking res.ok — fetch resolves successfully on 404 and 500 — your .json() then parses an error page and everything breaks downstream
  • Forgetting JSON.stringify on the body — passing an object directly sends "[object Object]" — stringify it and set the Content-Type header
  • Double await confusion — res.json() returns a promise too — you await fetch AND await .json()

§Frequently Asked Questions

Why doesn't fetch throw on a 404?

By design, fetch only rejects on network-level failures (offline, DNS, CORS). HTTP error statuses are 'successful' responses you must check via res.ok.

What is CORS and why is my fetch blocked?

Browsers block cross-origin requests unless the server sends Access-Control-Allow-Origin headers permitting your site. It's a server-side setting — you can't fix it purely from the client.

How do I send form data with fetch?

Pass a FormData object as body (no Content-Type header needed): fetch(url, { method: "POST", body: new FormData(form) }).

How do I cancel a slow fetch?

Use AbortSignal.timeout(ms) as the signal option, or an AbortController for manual cancellation.

🎓 Want to master JavaScript properly?This reference covers one topic — the full free course takes you from zero to real projects, with quizzes and a certificate.
Start the Free JavaScript Course →

§Related JavaScript Topics

← Async & Promises