/5 min read
Using the Fetch API in JavaScript
Beyond GET: JSON bodies, headers, credentials, CORS, cancellation with AbortController, streaming responses, and how Next.js extends fetch with caching controls.
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 properAccess-Control-Allow-Credentialsand 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.