Skip to main content

Fetch API

What is the fetch() API?​

The fetch() API is the standard way to make HTTP requests in the browser. It lets your JavaScript load data from a server, send form data, and react to responses without reloading the page.

Smallest working example​

fetch("/api/users")
.then(response => response.json())
.then(users => {
console.log(users);
});

Why this matters​

Most modern web apps need to load or send data:

  • Load a list of users, posts, or products
  • Submit a form without a full page refresh
  • Save changes to the server
  • Poll for updates in the background

Making a basic request​

The simplest use of fetch() is a GET request:

fetch("/api/users")
.then(response => response.json())
.then(users => {
console.log(users);
});

Understanding the response​

fetch() resolves to a Response object:

fetch("/api/users")
.then(response => {
console.log(response.status); // 200, 404, 500, and so on
console.log(response.ok); // true for 200-299
console.log(response.headers);

return response.json();
})
.then(data => {
console.log(data);
});

Handling JSON​

Most browser-to-server APIs use JSON:

fetch("/api/users")
.then(response => response.json())
.then(users => {
users.forEach(user => {
console.log(user.name);
});
});

Other response types​

fetch("/api/data.txt")
.then(response => response.text())
.then(text => {
console.log(text);
});
const image = document.querySelector("#avatar");

fetch("/api/image.jpg")
.then(response => response.blob())
.then(blob => {
if (image) {
image.src = URL.createObjectURL(blob);
}
});

Handling errors​

One important detail: fetch() only rejects for network-level problems. HTTP errors like 404 and 500 still give you a resolved Response.

fetch("/api/users")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}

return response.json();
})
.then(users => {
console.log(users);
})
.catch(error => {
console.error("Fetch failed:", error);
});
warning

Do not assume fetch() succeeded just because the promise resolved. Check response.ok before treating the request as successful.

Using async / await​

fetch() often reads more clearly with async / await:

async function loadUsers() {
try {
const response = await fetch("/api/users");

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

const users = await response.json();
console.log(users);
} catch (error) {
console.error("Failed to load users:", error);
}
}

Showing loading and error states​

Good UI usually shows what is happening while data loads:

async function loadUsers() {
const loadingElement = document.querySelector("#loading");
const errorElement = document.querySelector("#error");
const usersElement = document.querySelector("#users");

if (loadingElement) {
loadingElement.hidden = false;
}

if (errorElement) {
errorElement.hidden = true;
errorElement.textContent = "";
}

if (usersElement) {
usersElement.innerHTML = "";
}

try {
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error("Failed to load users");
}

const users = await response.json();

if (usersElement) {
users.forEach(user => {
const listItem = document.createElement("li");
listItem.textContent = user.name;
usersElement.appendChild(listItem);
});
}
} catch (error) {
if (errorElement) {
errorElement.hidden = false;
errorElement.textContent = "Failed to load users";
}
} finally {
if (loadingElement) {
loadingElement.hidden = true;
}
}
}

Sending data with fetch()​

Sending JSON​

fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
name: "Alice",
email: "alice@example.com"
})
})
.then(response => response.json())
.then(data => {
console.log("User created:", data);
});

Sending form data​

Learn more about forms and user input in the events section.

const form = document.querySelector("form");

if (form) {
form.addEventListener("submit", async event => {
event.preventDefault();

const formData = new FormData(form);

const response = await fetch("/api/submit", {
method: "POST",
body: formData
});

const result = await response.json();
console.log("Submitted:", result);
});
}

Other HTTP methods​

fetch("/api/users/123", {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "Bob" })
});
fetch("/api/users/123", {
method: "DELETE"
});
fetch("/api/users/123", {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "Charlie" })
});

Common patterns​

Reusable request helper​

async function apiRequest(url, options = {}) {
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options.headers
}
});

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

return response.json();
}

Parallel requests​

const [user, posts] = await Promise.all([
fetch("/api/user/123").then(response => response.json()),
fetch("/api/user/123/posts").then(response => response.json())
]);

Learn more about Promise.all().

Polling for updates​

If your page needs to check for new data regularly, fetch() is often paired with a timer:

function updateUI(data) {
console.log("Update UI with:", data);
}

async function pollForUpdates() {
try {
const response = await fetch("/api/updates");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}

const data = await response.json();
updateUI(data);
} catch (error) {
console.error("Polling failed:", error);
}
}

const pollIntervalId = setInterval(pollForUpdates, 5000);

function stopPolling() {
clearInterval(pollIntervalId);
}

Security and browser rules​

CORS​

Browsers block many cross-origin requests unless the server allows them:

fetch("https://api.example.com/data")
.catch(error => {
console.error("CORS or network error:", error);
});

Same-origin requests​

Requests to the same origin are the simplest case:

fetch("/api/data")
.then(response => response.json())
.then(data => {
console.log(data);
});

Avoid unsafe HTML insertion​

const commentsContainer = document.querySelector("#comments");

fetch("/api/comments")
.then(response => response.json())
.then(comments => {
if (!commentsContainer) {
return;
}

comments.forEach(comment => {
const paragraph = document.createElement("p");
paragraph.textContent = comment.text;
commentsContainer.appendChild(paragraph);
});
});

Summary​

  • fetch() is the standard browser API for loading and sending data.
  • Always check response.ok before treating a response as successful.
  • Use loading states and error states so the UI stays understandable while requests are in flight.
  • fetch() works well with async / await, FormData, and patterns like polling.

Next up​