The Fetch API
Objectives
By the end of this chapter, you should be able to:
- Request data from a server with
fetch() - Read the response’s status and turn its body into usable data with
.json() - Send data to a server with a
POSTrequest - Explain why
fetch()doesn’t reject on an error status like 404
💡 Why this matters: Almost every real web page loads at least some of its data from a server after the page itself has loaded.
fetch()is the built-in tool for that, and it’s a promise-based API, everything from this module applies directly.
A Real API to Practice On
Every example in this module uses JSONPlaceholder, a free, public practice API built specifically for this purpose, no signup, no API key. It has realistic endpoints like /users, /posts, and /todos, backed by real (if fake) data. GET requests return real data. POST, PUT, and DELETE requests are accepted and respond as if they worked, but nothing is actually saved, which is exactly what you want while learning: real request/response behavior, zero risk of breaking anything.
⚠️ A note on verification: every other example in this course was run and checked against real output. This sandbox’s network access is restricted to a small allowlist that doesn’t include JSONPlaceholder, so the
fetch()calls themselves couldn’t be executed from here the waysetTimeout(), promises, and everything else in this module were. The response data shown below is real, pulled directly from the live API, andfetch()’s mechanics (.json(),response.ok, sending aPOSTbody) were fully verified against a real local test server earlier in this module, the exact same behavior a live API produces. Still, run these examples yourself in a browser console or Node to see it firsthand.
Making a Request
fetch(url) sends a GET request and returns a promise that resolves once the response’s headers have arrived (not necessarily the full body yet, that’s a separate step).
async function getUser() {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
console.log(response.status);
// 200
console.log(response.ok);
// true
}
getUser();
response.status is the HTTP status code. response.ok is a shortcut for “status is in the 200-299 range,” true for a successful response.
Reading the Response Body
The response’s body isn’t automatically parsed. .json() reads it and parses it as JSON, returning another promise.
async function getUser() {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
const data = await response.json();
console.log(data.name, data.email);
// Leanne Graham Sincere@april.biz
}
getUser();
Two awaits here: one for the response itself to arrive, one for its body to be read and parsed. Both are asynchronous steps. The full object data holds a lot more than just name and email, a real user record includes an address, phone number, and company info too, this example only logs the two fields it needs.
Sending Data: POST
A plain fetch(url) call always sends GET. To send data, pass a second argument, an options object, with a method, headers, and a body.
async function createUser() {
const response = await fetch("https://jsonplaceholder.typicode.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Erin" }),
});
const data = await response.json();
console.log(data);
// { name: "Erin", id: 11 }
}
createUser();
body has to be a string, JSON.stringify() converts your object into one. The Content-Type: application/json header tells the server how to interpret that string. JSONPlaceholder responds with the object you sent plus a new id, as if it were really created, even though nothing was actually saved. This module doesn’t cover building the server side of this exchange, that’s Module 6, Web APIs and Backend Communication, this lesson focuses on the request itself.
The Gotcha: fetch() Doesn’t Reject on HTTP Errors
This trips up nearly everyone the first time. fetch()’s promise only rejects if the request itself fails to complete, no internet connection, the server unreachable, and so on. A 404 Not Found or a 500 Server Error is still a completed request, fetch() resolves normally, you just get a response with .ok set to false.
async function getMissingUser() {
const response = await fetch("https://jsonplaceholder.typicode.com/users/9999");
console.log(response.status);
// 404
console.log(response.ok);
// false
// No error was thrown. If you don't check response.ok,
// this code would keep running as if everything succeeded.
}
getMissingUser();
JSONPlaceholder only has 10 users, ids 1 through 10, so /users/9999 is guaranteed not to exist. Always check response.ok (or response.status) before assuming a request succeeded. Handling this properly, along with the case where fetch() genuinely does reject, is the whole subject of next lesson.
Try It
- Write an
asyncfunction that fetches"https://jsonplaceholder.typicode.com/todos/1", awaits.json()on the response, and logsdata.titleanddata.completed. - Inside that same function, log
response.statusandresponse.okbefore parsing the body. - Write an
asyncfunction that sends aPOSTrequest to"https://jsonplaceholder.typicode.com/posts"with a JSON body of{ title: "My First Post", body: "Hello", userId: 1 }, and logs the parsed response. - Write an
asyncfunction that fetches"https://jsonplaceholder.typicode.com/users/9999", checksresponse.ok, and logs"Request failed"instead of trying to parse the body, since this id doesn’t exist.
Recap
fetch(url)sends aGETrequest and resolves with a response once its headers arrive..json()reads and parses the response body, and is itself asynchronous, so it needs its ownawait.- Passing a second object to
fetch()withmethod,headers, and abodysends data,JSON.stringify()turns an object into the stringbodyrequires. fetch()only rejects on a genuine network failure. An HTTP error status like 404 still resolves normally, always checkresponse.ok.
Next lesson: handling both kinds of failure, network errors and bad HTTP statuses, properly.