Fetch API: Talking to a Server From the Browser
· 4 min read
Everything in the last two lessons about Promises and async/await was building toward this.
Everything in the last two lessons about Promises and async/await was building toward this: actually talking to a server from inside the browser, using the Fetch API. This is how a webpage loads fresh data without a full page reload — the mechanism behind every "load more," live search, and dynamic dashboard you've used.
Making A Request With Fetch
fetch() takes a URL, starts a network request, and returns a Promise that resolves once the response headers arrive (not necessarily once you have the actual data yet — that's an extra step, explained below).
fetch("https://api.example.test/users/1")
.then(function (response) {
return response.json();
})
.then(function (user) {
console.log("User:", user.name);
})
.catch(function (error) {
console.log("Request failed:", error);
});
Notice the two .then() steps. The first resolves with a Response object — this carries status codes and headers, but not the parsed data yet. Calling response.json() parses the response body as JSON and returns another Promise, which is why there's a second .then(). Skipping that step, and trying to read properties directly off the Response object, is a very common beginner mistake — you'd get undefined instead of your actual data.
The same request with async/await, which is how you'll typically see this written in real projects today:
async function loadUser() {
try {
const response = await fetch("https://api.example.test/users/1");
const user = await response.json();
console.log("User:", user.name);
} catch (error) {
console.log("Request failed:", error);
}
}
Checking Response Status
Here's a detail that genuinely surprises beginners: fetch's Promise only rejects on a real network failure — like no internet connection or a blocked request. A server responding with an error status, like 404 Not Found or 500 Internal Server Error, is still a "successful" fetch as far as the Promise is concerned. You have to check response.ok or response.status yourself.
async function loadUserSafely() {
const response = await fetch("https://api.example.test/users/999");
if (!response.ok) {
console.log("Server returned an error:", response.status);
return;
}
const user = await response.json();
console.log(user);
}
response.ok is true for status codes 200–299, and false otherwise. Skipping this check is a real, common bug — code that calls response.json() on an error response page, and then crashes trying to use data that was never actually returned successfully.
Sending Data With Fetch
Sending data, not just reading it, needs a second argument to fetch with the request method, headers, and a body.
async function createPost() {
const response = await fetch("https://api.example.test/posts", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
title: "My first post",
author: "Vikesh Singh"
})
});
const result = await response.json();
console.log(result);
}
Three things have to line up correctly here, and forgetting any one of them is a classic bug: the method must match what the server expects (GET is the default if you omit method, which silently breaks a POST request you intended); the Content-Type header tells the server the body is JSON, so it parses it correctly; and JSON.stringify() is required because fetch sends the body as text — passing a plain JavaScript object directly, without stringifying it, sends the useless string "[object Object]" instead of your actual data.
A practical, real-world tip: combine this with the form-handling pattern from the events lesson. A form's submit handler calls event.preventDefault(), reads the field values, and sends them with fetch — this exact combination, forms plus fetch plus async/await, is one of the most common building blocks in real interactive sites, letting a page submit data without ever reloading.
Understanding CORS Errors
It's also worth knowing what CORS is, since it's one of the most common fetch errors beginners hit and panic over. Browsers block a webpage running on one domain from freely reading responses from a different domain's server, unless that server explicitly allows it through special response headers — this is a security feature, not a bug in your code. If you see an error mentioning "CORS policy" in the console, it almost always means the server you're fetching from hasn't allowed requests from your page's origin, and the fix has to happen on that server's side, not in your JavaScript.
A final practical habit: get used to reading a Network tab request's full details, not just whether it succeeded. The request's actual URL, the headers you sent, and the exact response body are all visible there, and comparing what you intended to send against what the browser actually sent is often the fastest way to catch a typo in a URL, a missing header, or a malformed request body before it causes confusion further down in your code.
Worth trying right now: pick any free public test API (many exist specifically for practice, returning fake JSON data), fetch a single resource with async/await, check response.ok before parsing, and log the result. Then deliberately fetch a URL that doesn't exist and observe how your error handling responds differently to a network failure versus a normal 404 response.
KEEP READING