22 minLesson 25 of 35
Asynchronous JavaScript
Fetch API & Working with REST APIs
Fetch API & Working with REST APIs
The Fetch API is JavaScript's built-in way to make HTTP requests — no libraries required. It returns Promises, works seamlessly with async/await, and handles everything from simple data fetching to file uploads and streaming responses.
Basic GET Request
// Returns a Promise that resolves to a Response object
const response = await fetch("https://api.example.com/users");
// Response has status info
response.status; // 200, 404, 500, etc.
response.ok; // true if status is 200-299
response.headers; // Headers object
// Parse the body (also returns a Promise)
const data = await response.json(); // parse as JSON
const text = await response.text(); // parse as plain text
const blob = await response.blob(); // parse as binary
Proper Error Handling
fetch only rejects on network errors. A 404 or 500 response resolves successfully — you must check response.ok:
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
// Try to get error details from the response body
const error = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(error.message ?? `HTTP ${response.status}`);
}
return response.json();
}
// Usage
try {
const user = await getUser(42);
displayUser(user);
} catch (err) {
if (err.message.includes("404")) {
showNotFound();
} else {
showError(err.message);
}
}
POST, PUT, DELETE Requests
const BASE_URL = "https://api.example.com";
// POST — create a resource
async function createUser(userData) {
const response = await fetch(`${BASE_URL}/users`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${getToken()}`
},
body: JSON.stringify(userData)
});
if (!response.ok) throw new Error("Failed to create user");
return response.json();
}
// PUT — replace a resource
async function updateUser(id, data) {
const response = await fetch(`${BASE_URL}/users/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error("Failed to update");
return response.json();
}
// PATCH — partial update
async function patchUser(id, changes) {
const response = await fetch(`${BASE_URL}/users/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(changes)
});
if (!response.ok) throw new Error("Failed to patch");
return response.json();
}
// DELETE
async function deleteUser(id) {
const response = await fetch(`${BASE_URL}/users/${id}`, {
method: "DELETE",
headers: { "Authorization": `Bearer ${getToken()}` }
});
if (!response.ok) throw new Error("Failed to delete");
// DELETE often returns 204 No Content (no body to parse)
}
Building an API Client
Wrap fetch in a class for reuse:
class ApiClient {
constructor(baseUrl, getToken) {
this.baseUrl = baseUrl;
this.getToken = getToken;
}
async request(path, options = {}) {
const url = `${this.baseUrl}${path}`;
const token = this.getToken?.();
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...(token && { "Authorization": `Bearer ${token}` }),
...options.headers
}
});
if (response.status === 204) return null; // No Content
const data = await response.json().catch(() => null);
if (!response.ok) {
const message = data?.message ?? `HTTP ${response.status}`;
const error = new Error(message);
error.status = response.status;
error.data = data;
throw error;
}
return data;
}
get(path, params = {}) {
const query = new URLSearchParams(params).toString();
return this.request(`${path}${query ? `?${query}` : ""}`);
}
post(path, body) {
return this.request(path, { method: "POST", body: JSON.stringify(body) });
}
put(path, body) {
return this.request(path, { method: "PUT", body: JSON.stringify(body) });
}
patch(path, body) {
return this.request(path, { method: "PATCH", body: JSON.stringify(body) });
}
delete(path) {
return this.request(path, { method: "DELETE" });
}
}
// Usage
const api = new ApiClient("https://api.example.com", () => localStorage.getItem("token"));
const users = await api.get("/users", { page: 1, limit: 20 });
const newUser = await api.post("/users", { name: "Alice", email: "alice@example.com" });
await api.delete(`/users/${id}`);
Query Parameters
// URLSearchParams for clean query string building
const params = new URLSearchParams({
page: 1,
limit: 20,
sort: "name",
filter: "active"
});
params.toString(); // "page=1&limit=20&sort=name&filter=active"
fetch(`/api/users?${params}`);
// Append multiple values for same key
const tags = new URLSearchParams();
["javascript", "react", "nextjs"].forEach(tag => tags.append("tag", tag));
// "tag=javascript&tag=react&tag=nextjs"
File Upload
// FormData for multipart/form-data uploads
const fileInput = document.querySelector("#file");
async function uploadFile(file) {
const formData = new FormData();
formData.append("file", file);
formData.append("category", "avatar");
// Don't set Content-Type header — browser sets it with boundary
const response = await fetch("/api/upload", {
method: "POST",
body: formData
});
if (!response.ok) throw new Error("Upload failed");
return response.json();
}
AbortController — Cancellable Requests
// Cancel a request in progress
const controller = new AbortController();
// Cancel if user navigates away
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch("/api/slow-endpoint", {
signal: controller.signal
});
const data = await response.json();
} catch (err) {
if (err.name === "AbortError") {
console.log("Request cancelled");
} else {
throw err;
}
}
// React pattern: cancel on component unmount
useEffect(() => {
const controller = new AbortController();
fetch("/api/data", { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(err => {
if (err.name !== "AbortError") setError(err);
});
return () => controller.abort(); // cleanup
}, []);
Next lesson: Classes & Prototype Chain — understanding JavaScript's object-oriented system.
📱
Get Notes Free →Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises