Talking to servers is the core of most web apps, and fetch is the standard API in browsers and Node.js.
fetch(url, options) makes an HTTP request and returns a promise for a Response. It's built into browsers and into Node.js 18+.
const res = await fetch("https://api.example.com/users/1");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();The crucial detail: fetch only rejects on network failure (offline, DNS error, CORS refusal, abort). An HTTP error like 404 or 500 still fulfils — you must check res.ok (status 200–299) or res.status yourself.
Reading the body is a second async step: res.json(), res.text(), res.blob() or res.arrayBuffer(). A body can only be read once.
To send data, set the method, headers and body:
await fetch("/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "Write docs" }),
});For form uploads, pass a FormData as the body and let fetch set the content type. Query strings are easiest to build with new URL(base) and url.searchParams.set(...), which handles encoding.
Cancellation uses an AbortSignal: pass { signal: controller.signal } and call controller.abort(), or use AbortSignal.timeout(5000) for a timeout. The promise then rejects with an AbortError or TimeoutError.
In browsers, requests to another origin are subject to CORS: the server must allow your origin, otherwise the browser blocks the response and fetch rejects with a generic TypeError. Cookies are sent for same-origin requests by default; cross-origin needs credentials: "include".
Example
// Runs in Node.js 18+ or a browser module
async function getJSON(url, { timeoutMs = 5000 } = {}) {
const res = await fetch(url, {
headers: { Accept: "application/json" },
signal: AbortSignal.timeout(timeoutMs),
});
if (!res.ok) {
throw new Error(`Request failed: ${res.status} ${res.statusText}`);
}
return res.json();
}
const url = new URL("https://jsonplaceholder.typicode.com/posts");
url.searchParams.set("userId", "1");
try {
const posts = await getJSON(url);
console.log(posts.length, posts[0].title);
} catch (err) {
if (err.name === "TimeoutError") console.error("Too slow");
else console.error(err.message);
}Remember
- fetch rejects only on network errors — always check
res.ok - Reading the body (
res.json()) is a separate await - Send JSON with method, Content-Type header and
JSON.stringifybody; cancel with AbortSignal
Watch out for
- Assuming a 404 or 500 response makes fetch throw.