Using the Fetch API in JavaScript
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.