Codeskill

Learn to code, step by step

Fetch API in depth

The Fetch API – request options, headers, sending JSON and form data, reading responses, and CORS at the level you need when wiring a real front end to an API.

Request shape

const res = await fetch('/api/items', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({ name: 'Widget', qty: 2 }),
  credentials: 'same-origin',
});

credentials controls cookies: omit, same-origin (default), or include for cross-origin cookies when the server allows it.

Reading the response

const type = res.headers.get('Content-Type') ?? '';

let data;
if (type.includes('application/json')) {
  data = await res.json();
} else {
  data = await res.text();
}

You can only read the body once. Clone with res.clone() if you need both text and json (rare).

FormData and uploads

const form = document.querySelector('#upload-form');
form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const body = new FormData(form);
  const res = await fetch('/api/upload', { method: 'POST', body });
  if (!res.ok) throw new Error('Upload failed');
});

Do not set Content-Type manually for FormData – the browser adds the multipart boundary.

URLSearchParams for query strings

const params = new URLSearchParams({
  q: searchTerm,
  page: String(page),
});
const res = await fetch(`/api/search?${params}`);

CORS in one paragraph

Browsers block JavaScript from reading many cross-origin responses unless the server sends permissive CORS headers. You will see opaque network errors or CORS messages in the console. Fixing it is a server job (Access-Control-Allow-Origin, methods, headers). From the client, use the correct URL, avoid unnecessary custom headers on simple GETs, and during development consider a local proxy if you control the front end but not the API yet.

A small API helper

async function api(path, { method = 'GET', body, headers = {} } = {}) {
  const init = {
    method,
    headers: { Accept: 'application/json', ...headers },
  };
  if (body !== undefined) {
    init.headers['Content-Type'] = 'application/json';
    init.body = JSON.stringify(body);
  }
  const res = await fetch(path, init);
  if (!res.ok) {
    const message = await res.text().catch(() => '');
    throw new Error(`${method} ${path} failed: ${res.status} ${message}`);
  }
  if (res.status === 204) return null;
  return res.json();
}

Centralising fetch logic saves repeating status checks and JSON parsing in every caller.

Next: DOM performance – batching updates, observers, and avoiding layout thrash.

PreviousAsync patterns (errors, cancellation)