Skip to main content
Juliano Alves
Back to blog

Using the Fetch API in JavaScript

5 min read
By Juliano Alves

The Fetch API replaced XMLHttpRequest as the standard for HTTP from browser JavaScript and is now ubiquitous in Node 18+ and edge runtimes as well. It is promise-based, composes well with async/await, and pairs cleanly with modern cancellation and streaming APIs.

This post builds from a basic GET to production-minded patterns.

Basic GET with error handling#

fetch resolves on HTTP errors (404, 500)—you must check response.ok or response.status.

async function loadJson(url) {
  const response = await fetch(url, {
    method: 'GET',
    headers: { Accept: 'application/json' },
  });

  if (!response.ok) {
    const text = await response.text().catch(() => '');
    throw new Error(`HTTP ${response.status}: ${text.slice(0, 200)}`);
  }

  return response.json();
}

Wrapping this once in your app avoids repeating boilerplate.

POST with JSON body#

const response = await fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: 'Ada', role: 'admin' }),
});

For FormData, omit Content-Type—the browser sets the multipart boundary automatically.

Credentials and CORS#

  • credentials: 'include' sends cookies on cross-origin requests only if the server responds with proper Access-Control-Allow-Credentials and a non- * origin.
  • Same-site APIs often use same-origin (default for relative URLs in browsers).

Misconfigured CORS is a frequent source of “it works in Postman” bugs—always test from the browser origin.

Cancellation with AbortController#

Every in-flight request should be abortable when the user navigates away or retypes a search:

const controller = new AbortController();

fetch('/api/search?q=node', { signal: controller.signal })
  .then((r) => r.json())
  .then(console.log)
  .catch((err) => {
    if (err.name === 'AbortError') return; // expected
    throw err;
  });

// later: controller.abort();

In React, create the controller in useEffect and abort in the cleanup function.

Streaming responses#

For large downloads or NDJSON APIs, avoid await response.json()—use response.body as a ReadableStream:

const response = await fetch('/api/logs/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log(decoder.decode(value, { stream: true }));
}

This keeps memory flat compared to buffering the entire payload.

Next.js and extended fetch#

In Server Components, Next extends fetch with next: { revalidate, tags } for caching. That is not standard browser fetch—keep mental separation between edge/server usage and client bundles.

Retries and timeouts#

Fetch has no built-in timeout. Combine AbortController with setTimeout or use a thin wrapper / library for retry policies (429 backoff, network errors).

Summary#

Master fetch by layering: status checks, JSON vs streams, AbortController, and credentials rules. In frameworks like Next, read the platform docs for caching extensions—those knobs change semantics beyond the web standard.

© 2026 Juliano Alves. All rights reserved.