Home/Learn/JavaScript & TypeScript/fetch — Talking to Your API

fetch — Talking to Your API

Intermediate
Async

fetch is the built-in HTTP client. Its one surprising behaviour is that it does not reject on 404 or 500 — you have to check response.ok yourself.

Overview

Every frontend that talks to a backend does it through fetch, directly or through a wrapper. The API is small: a URL, an options object, and a response whose body you read with .json() or .text(). The trap that catches everyone once is that a rejected promise means the request never completed — a network failure or CORS block — while an HTTP error status is a perfectly successful fetch that happens to carry a 500. If you do not check response.ok, your error path never runs and you try to parse an error page as JSON.

The Shape of a Request

GET is the default. Anything else needs a method, usually a Content-Type, and a stringified body.

GET, JSON POST, file upload, cookies
// GET
const res = await fetch('/api/problems?page=1')

// POST with JSON
const res = await fetch('/api/problems', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${token}`,
  },
  body: JSON.stringify({ title, difficulty }),
})

// File upload — do NOT set Content-Type; the browser adds the boundary
const form = new FormData()
form.append('file', fileInput.files[0])
await fetch('/api/upload', { method: 'POST', body: form })

// Cookies are not sent cross-origin unless you ask
await fetch(url, { credentials: 'include' })

Error Handling That Actually Works

The rule: reject means the request failed to happen; !res.ok means it happened and the server said no.

Two different failures, two different handlers
async function api(path, options) {
  let res
  try {
    res = await fetch(path, options)
  } catch (err) {
    // Network down, DNS failure, CORS block — never reached the server
    throw new Error('Network error — check your connection')
  }

  if (!res.ok) {
    // The request succeeded; the server returned 4xx/5xx
    const body = await res.json().catch(() => ({}))
    throw new Error(body.detail ?? `HTTP ${res.status}`)
  }

  return res.status === 204 ? null : res.json()
}

// Without the !res.ok check, a 500 returning an HTML error page
// reaches res.json() and throws "Unexpected token <" instead.

Cancelling and Timing Out

AbortController cancels a request. In React this is what stops a stale response overwriting a newer one.

AbortController, and the stale-response fix
const controller = new AbortController()
fetch(url, { signal: controller.signal })
controller.abort()          // rejects with an AbortError

// Timeout, built in:
await fetch(url, { signal: AbortSignal.timeout(5000) })

// The React pattern — cancel on unmount or when the query changes
useEffect(() => {
  const c = new AbortController()
  fetch(`/api/search?q=${q}`, { signal: c.signal })
    .then(r => r.json())
    .then(setResults)
    .catch(err => { if (err.name !== 'AbortError') setError(err) })
  return () => c.abort()
}, [q])

Key Points to Remember

  • 1fetch rejects only on network failure — a 404 or 500 is a successful fetch, so always check response.ok
  • 2Without the ok check, an HTML error page reaches res.json() and throws a confusing parse error
  • 3Do not set Content-Type when sending FormData — the browser must add the multipart boundary
  • 4Cross-origin cookies are not sent unless you pass credentials: "include"
  • 5AbortController cancels in-flight requests; aborting on cleanup is what stops a stale response overwriting a newer one

Interview Questions

Sign in to ask Aria
1

Does fetch throw an error for a 404 response? What do you have to check?

Easy
2

How do you add a timeout to a fetch request?

Medium
3

Why should you not set the Content-Type header when uploading FormData?

Medium

Ask Aria about fetch — Talking to Your API

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…