Topic 11 / 15

fetch & Working with APIs

~10 min read  //  JavaScript Series  //  Coding India

GET — Reading Data

async function getUsers() {
  const response = await fetch("https://api.example.com/users");
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

const users = await getUsers();

The two-step is always the same: check response.ok, then parse the body. Remember: fetch only rejects on network failure — a 404 or 500 is a “successful” fetch with ok: false.

Query Parameters — Use URLSearchParams

const params = new URLSearchParams({
  q: "django tutorial",
  page: 2,
});
const response = await fetch(`/api/search?${params}`);
// /api/search?q=django+tutorial&page=2 — encoding handled for you

POST — Sending JSON

async function createCourse(course) {
  const response = await fetch("/api/courses", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${token}`,
    },
    body: JSON.stringify(course),
  });
  if (!response.ok) {
    const problem = await response.json().catch(() => ({}));
    throw new Error(problem.message ?? `HTTP ${response.status}`);
  }
  return response.json();
}

await createCourse({ title: "JS Mastery", price: 199 });

Other methods follow the same shape: PUT/PATCH to update, DELETE to remove.

A Reusable API Helper

Real apps wrap fetch once instead of repeating boilerplate:

async function api(path, { method = "GET", body, ...options } = {}) {
  const response = await fetch(`https://api.example.com${path}`, {
    method,
    headers: {
      "Content-Type": "application/json",
      ...options.headers,
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!response.ok) throw new Error(`HTTP ${response.status} on ${path}`);
  return response.status === 204 ? null : response.json();
}

// usage reads beautifully:
const users = await api("/users");
await api("/courses", { method: "POST", body: { title: "CSS" } });

The Full UI Pattern: Loading / Error / Data

async function showCourses() {
  spinner.hidden = false;
  errorBox.hidden = true;
  try {
    const courses = await api("/courses");
    renderList(courses);
  } catch (err) {
    errorBox.textContent = "Couldn't load courses. Retry?";
    errorBox.hidden = false;
  } finally {
    spinner.hidden = true;
  }
}

Every data-driven screen you’ll ever build — vanilla or React — is a variation of these three states.

Aborting Requests

const controller = new AbortController();
fetch("/api/big-search", { signal: controller.signal });
controller.abort();      // e.g. when the user types a new query