JavaScript Fetch API

Master the Fetch API for making HTTP requests in JavaScript, including GET, POST, error handling, and working with JSON responses.

TL;DR

  1. 01Use fetch() to make HTTP requests and await the response.
  2. 02Check response.ok, since fetch rejects only on network failure.
  3. 03Use async/await with try/catch for clean, readable fetch code.

Tips

  1. 01In Next.js App Router, prefer the built-in extended fetch, since it supports cache and revalidate options for data fetching.
  2. 02Always wrap fetch calls in try/catch and check response.ok, so both network failures and HTTP error responses get handled consistently.
  3. 03Use an AbortController to cancel in-flight requests on unmount, which prevents wasted network calls and stale state updates.

Warnings

  1. 01Calling response.json() on an error response that returns HTML, like a 404 page, throws a JSON parse error.
  2. 02Use res.text() as a safe fallback when the content type of a response is unknown or unconfirmed.
  3. 03Forgetting to set the Content-Type header on a POST request can cause the server to misparse the JSON body.

Basic GET Request

    fetch()

    Returns a Promise that resolves to a Response object, the entry point for every request.

    async function getUser(id) {
      const response = await fetch(`/api/users/${id}`);
      if (!response.ok) {
        throw new Error(`HTTP error: ${response.status}`);
      }
      return response.json();
    }
    response.ok

    True for status codes 200-299 — always check it before trusting the response.

    response.json()

    Parses the response body as JSON and returns another Promise.

    const data = await response.json();
    Network-only rejection

    fetch only rejects on network-level failures; a 404 or 500 still resolves successfully.

    Replaces XMLHttpRequest

    Fetch is the modern, promise-based replacement for the older XMLHttpRequest API.

POST Request with JSON Body

    Sending JSON

    Set method, headers, and body in the options object to send data.

    async function createPost(data) {
      const response = await fetch('/api/posts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      if (!response.ok) throw new Error('Request failed');
      return response.json();
    }
    method

    Sets the HTTP verb, like 'POST', 'PUT', or 'DELETE'.

    headers

    Sets request headers, most commonly Content-Type for JSON bodies.

    headers: { 'Content-Type': 'application/json' }
    body

    Holds the request payload, usually JSON.stringify(data) for JSON APIs.

    body: JSON.stringify(data)
    credentials

    Controls whether cookies are sent, e.g. 'include' or 'same-origin'.

Error Handling Patterns

    Robust try/catch

    Handle both network errors and HTTP error responses in one function.

    async function safeFetch(url, options = {}) {
      try {
        const res = await fetch(url, options);
        if (!res.ok) {
          const msg = await res.text();
          throw new Error(`${res.status}: ${msg}`);
        }
        return await res.json();
      } catch (err) {
        console.error('Fetch failed:', err);
        throw err;
      }
    }
    Network-level errors

    Wrap fetch in try/catch to catch network-level errors like being offline or CORS failures.

    HTTP error status

    Check response.ok inside the try block to catch HTTP errors (4xx, 5xx).

    Reading the error body

    Read res.text() or res.json() on error responses to get the server's error message.

    const msg = await res.text();

Common Fetch Patterns

    Auth token

    Send a bearer token in the Authorization header.

    headers: { Authorization: 'Bearer ' + token }
    Form data

    Send a FormData body directly — no Content-Type header needed.

    body: new FormData(formEl)
    Abort a request

    Cancel an in-flight request with AbortController.

    const ac = new AbortController();
    fetch(url, { signal: ac.signal });
    Read plain text

    Read a non-JSON response body as text.

    const text = await response.text();
    Download a blob

    Read a binary response body as a Blob.

    const blob = await response.blob();

Fetch vs Alternatives

    fetch()

    Best for simple requests with no extra dependencies; error handling is more verbose and there are no interceptors.

    axios

    Best for complex apps needing interceptors and retries; adds an external dependency of about 15 kB gzipped.

    SWR / React Query

    Best for data fetching in React with built-in caching; framework-specific and needs more setup.

    tRPC

    Best for full-stack TypeScript with end-to-end types; requires a matching server setup.

In Practice

FAQ