The Fetch API is the modern, built-in way to make HTTP requests from JavaScript — fetching data from a server, submitting a form without a page reload, or calling a third-party API. It replaced the older, clunkier XMLHttpRequest and returns a Promise, so it fits naturally with Promises and async/await.

At its simplest, fetch(url) sends a GET request and resolves with a Response object once the server responds. That response isn't the actual data yet — you still have to extract the body, usually as JSON.

A Basic GET Request

fetch("https://jsonplaceholder.typicode.com/users/1")
  .then((response) => response.json()) // parse the body as JSON
  .then((data) => console.log(data.name))
  .catch((error) => console.error("Request failed:", error));

Output:

Leanne Graham

Notice the two .then() calls: the first converts the raw response into usable JSON (itself an async step, since the body can still be streaming in), and the second works with the actual data.

The Same Request with async/await

Most modern code wraps fetch in an async function instead of chaining .then(), since it reads top-to-bottom like synchronous code:

async function getUser() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
    const data = await response.json();
    console.log(data.name);
  } catch (error) {
    console.error("Request failed:", error);
  }
}

getUser();

Output:

Leanne Graham

Handling Errors Correctly

A common mistake: fetch() only rejects on a network failure (no connection, DNS error, etc.) — it does not reject on a 404 or 500 response. The request "succeeded" from fetch's point of view; the server just sent back an error status. You need to check response.ok yourself:

async function getUserSafely(id) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);

  if (!response.ok) {
    throw new Error(`Request failed with status ${response.status}`);
  }

  return response.json();
}

Sending Data with POST

To send data, pass a second options object with a method, headers, and a JSON-stringified body:

fetch("https://jsonplaceholder.typicode.com/posts", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ title: "Hello", body: "World", userId: 1 }),
})
  .then((res) => res.json())
  .then((data) => console.log(data.id));

Once you're comfortable with the basics here, the next thing worth understanding is exactly how JavaScript schedules this asynchronous work behind the scenes — it explains why your fetch callback never runs in the middle of other synchronous code, no matter how fast the network responds.