Talk to a Server
JavaScript fetch
`fetch` makes HTTP requests. Returns a Promise that resolves to a Response — and there's one gotcha around error checking.
What you'll learn
- Make a GET request with `fetch`
- Parse a JSON response
- Check `response.ok` for HTTP errors
- Make a POST request with a JSON body
fetch is the modern way to make HTTP requests from JavaScript. It
works in browsers and modern Node.js. It returns a Promise that
resolves to a Response object.
A Basic GET
const response = await fetch("https://api.example.com/users/1");
const user = await response.json();
console.log(user); Two awaits — one for the response headers, one for the response
body. .json() reads the body and parses it as JSON.
The response.ok Gotcha
fetch only rejects on network failure — DNS failures, dropped
connections, CORS issues. It does NOT reject on HTTP error statuses
like 404 or 500.
const response = await fetch("/api/users/1");
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const user = await response.json(); POST With a JSON Body
To send data, pass an options object as the second argument.
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Ada", role: "admin" }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const created = await response.json();
console.log(created); Three things to remember when posting JSON:
method: "POST".Content-Type: application/jsonheader.JSON.stringify(...)the body —fetchsends strings, not objects.
Other Response Bodies
The Response object has several body readers:
| Method | Returns |
|---|---|
response.json() | Parsed JSON |
response.text() | Raw text |
response.blob() | Binary data (Blob) |
response.arrayBuffer() | Binary as ArrayBuffer |
response.formData() | FormData (for forms) |
You can only consume the body once — it’s a stream. To read it
both as text and as JSON, clone first with response.clone().
Aborting a Request
If a request might run long, use AbortController to cancel it.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch("/slow", { signal: controller.signal });
const data = await response.json();
console.log(data);
} catch (e) {
if (e.name === "AbortError") console.log("timed out");
else throw e;
} finally {
clearTimeout(timeout);
} This is the standard pattern for timeouts on fetch.
A Full Wrapper
A reusable helper that handles the common cases:
async function api(path, options = {}) {
const response = await fetch(path, {
headers: { "Content-Type": "application/json" },
...options,
body: options.body ? JSON.stringify(options.body) : undefined,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
// Use:
const user = await api("/api/users/1");
const created = await api("/api/users", {
method: "POST",
body: { name: "Ada" },
}); Most real apps end up with something like this wrapping fetch.
Try It Yourself
Exercise
Fetch With Error Handling
async function getJSON(url) {
// your code
}
✅ Show solution
async function getJSON(url) {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return res.json();
}
Up Next
What to do when something goes wrong — error handling, try/catch/finally.