How to Use the JS Fetch API Complete Tutorial
A complete tutorial on the JavaScript Fetch API. Covers GET and POST requests, response handling, JSON parsing, headers configuration, error handling, AbortController for cancellation, query parameters, timeout patterns, and migrating from XMLHttpRequest to fetch.
The Fetch API is the modern, Promise-based way to make HTTP requests in JavaScript. It replaces XMLHttpRequest with a cleaner interface that works naturally with async/await. Available in all modern browsers and Node.js 18+, Fetch is the standard tool for API communication.
Basic GET Request
const response = await fetch("https://api.example.com/users");
const users = await response.json();
console.log(users);fetch() returns a Promise that resolves to a Response object. The body must be read separately using .json(), .text(), .blob(), or .arrayBuffer().
Response Object
| Property/Method | Type | Description |
|---|---|---|
response.ok | boolean | true if status is 200-299 |
response.status | number | HTTP status code (200, 404, etc.) |
response.statusText | string | Status message ("OK", "Not Found") |
response.headers | Headers | Response headers object |
response.json() | Promise | Parse body as JSON |
response.text() | Promise | Parse body as text |
response.blob() | Promise | Parse body as Blob |
response.clone() | Response | Clone for reading body twice |
Error Handling
Fetch does not reject on HTTP errors (404, 500). It only rejects on network failures:
async function fetchJSON(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
try {
const data = await fetchJSON("/api/users/999");
console.log(data);
} catch (error) {
console.error("Request failed:", error.message);
// "HTTP 404: Not Found" for missing resources
// "Failed to fetch" for network errors
}This is the most important Fetch gotcha: always check response.ok or response.status.
POST Request With JSON Body
async function createUser(userData) {
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}
const newUser = await createUser({
name: "Alice",
email: "alice@example.com",
});See handling POST requests with JS fetch API guide for advanced POST patterns.
All HTTP Methods
// PUT (full update)
await fetch("/api/users/42", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice", email: "newemail@example.com" }),
});
// PATCH (partial update)
await fetch("/api/users/42", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "newemail@example.com" }),
});
// DELETE
await fetch("/api/users/42", {
method: "DELETE",
});Headers
// Using Headers constructor
const headers = new Headers();
headers.append("Authorization", "Bearer token123");
headers.append("Accept", "application/json");
const response = await fetch("/api/protected", { headers });
// Using plain object (simpler)
const response2 = await fetch("/api/protected", {
headers: {
Authorization: "Bearer token123",
Accept: "application/json",
},
});
// Reading response headers
console.log(response.headers.get("Content-Type"));
console.log(response.headers.get("X-Request-Id"));Query Parameters
Build URLs with URLSearchParams:
const params = new URLSearchParams({
q: "javascript",
page: 2,
sort: "date",
limit: 20,
});
const response = await fetch(`/api/search?${params}`);
// Fetches: /api/search?q=javascript&page=2&sort=date&limit=20AbortController (Cancellation)
Cancel in-flight requests with AbortController:
const controller = new AbortController();
// Start the request
const fetchPromise = fetch("/api/large-data", {
signal: controller.signal,
});
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetchPromise;
const data = await response.json();
} catch (error) {
if (error.name === "AbortError") {
console.log("Request was cancelled");
} else {
throw error;
}
}Timeout Helper
async function fetchWithTimeout(url, options = {}, timeoutMs = 10000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
return response;
} finally {
clearTimeout(timeoutId);
}
}
// 3-second timeout
const response = await fetchWithTimeout("/api/data", {}, 3000);Sending Form Data
// URL-encoded form data
const formData = new URLSearchParams();
formData.append("username", "alice");
formData.append("password", "secret123");
await fetch("/api/login", {
method: "POST",
body: formData,
// Content-Type is set automatically to application/x-www-form-urlencoded
});
// Multipart form data (for file uploads)
const multipart = new FormData();
multipart.append("name", "Alice");
multipart.append("avatar", fileInput.files[0]);
await fetch("/api/profile", {
method: "POST",
body: multipart,
// Do NOT set Content-Type — browser sets it with boundary
});See uploading files via JS fetch API complete guide for file upload patterns.
Parallel Requests
// Fetch multiple endpoints concurrently
const [users, posts, comments] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
fetch("/api/comments").then(r => r.json()),
]);Reusable Fetch Wrapper
class ApiClient {
constructor(baseUrl, defaultHeaders = {}) {
this.baseUrl = baseUrl;
this.defaultHeaders = {
"Content-Type": "application/json",
...defaultHeaders,
};
}
async request(path, options = {}) {
const response = await fetch(`${this.baseUrl}${path}`, {
...options,
headers: { ...this.defaultHeaders, ...options.headers },
});
if (!response.ok) {
const body = await response.json().catch(() => null);
throw new Error(body?.message || `HTTP ${response.status}`);
}
return response.json();
}
get(path) { return this.request(path); }
post(path, data) { return this.request(path, { method: "POST", body: JSON.stringify(data) }); }
put(path, data) { return this.request(path, { method: "PUT", body: JSON.stringify(data) }); }
patch(path, data) { return this.request(path, { method: "PATCH", body: JSON.stringify(data) }); }
delete(path) { return this.request(path, { method: "DELETE" }); }
}
const api = new ApiClient("https://api.example.com", {
Authorization: "Bearer token123",
});
const users = await api.get("/users");
const newUser = await api.post("/users", { name: "Alice" });Rune AI
Key Insights
- Fetch does not reject on HTTP errors: Only network failures cause rejection; always check
response.okorresponse.statusbefore reading the body - Body methods can only be called once: Use
response.clone()if you need to read the body multiple times - AbortController enables cancellation: Pass a signal to cancel requests on timeout, component unmount, or user action
- Do not set Content-Type for FormData: The browser must set it automatically to include the multipart boundary string
- Build a reusable wrapper: Centralize base URL, headers, error handling, and auth token management in a single ApiClient class
Frequently Asked Questions
Does fetch send cookies by default?
How do I download a file with fetch?
Can I track upload progress with fetch?
Is fetch available in Node.js?
How is fetch different from Axios?
Conclusion
The Fetch API is the standard for HTTP requests in modern JavaScript. Always check response.ok for error handling, use AbortController for timeouts and cancellation, and build a reusable wrapper class for consistent API communication. For POST-specific patterns, see handling POST requests with JS fetch API guide. For the async architecture that powers Fetch, see the JS event loop architecture complete guide.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.