On this page
Fetch API
What is the Fetch API?
fetch() is the browser's native API for making HTTP requests. It replaces the old XMLHttpRequest with a promise-based, cleaner and more modern interface.
const response = await fetch(url, options);It returns a Response that contains the HTTP status, headers, and methods for reading the body.
HTTP methods
| Method | Use | Has body? |
|---|---|---|
GET |
Retrieve data | No |
POST |
Create a resource | Yes |
PUT |
Replace a resource | Yes |
PATCH |
Partially update | Yes |
DELETE |
Delete a resource | Generally no |
The Response object
The fetch response has important properties and methods:
| Property/Method | Description |
|---|---|
response.ok |
true if status is 200-299 |
response.status |
HTTP code (200, 404, 500, etc.) |
response.statusText |
Status text ("OK", "Not Found") |
response.headers |
Response headers |
response.json() |
Reads the body as JSON (promise) |
response.text() |
Reads the body as text (promise) |
response.blob() |
Reads the body as Blob (promise) |
Important note about .ok
fetch does not reject the promise on HTTP errors (404, 500, etc.). It only rejects on network errors (no connection, DNS failure). Always check response.ok:
const res = await fetch('/api/data');
if (!res.ok) {
throw new Error(`HTTP Error: ${res.status}`);
}
const data = await res.json();Headers and body
To send data, configure the headers and body:
const res = await fetch('/api/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123',
},
body: JSON.stringify({ name: 'Item', price: 42 }),
});The body is serialized with JSON.stringify(). The headers tell the server the format of the data.
AbortController
Allows you to cancel in-flight requests. It is essential for:
- Search fields — Cancel the previous request when typing
- Navigation — Cancel pending requests when changing pages
- Timeouts — Cancel requests that take too long
const controller = new AbortController();
fetch(url, { signal: controller.signal });
controller.abort(); // cancels the requestURLSearchParams
For building query strings safely (with automatic encoding):
const params = new URLSearchParams({
search: 'hello world',
page: '1',
});
console.log(params.toString()); // "search=hello+world&page=1"Reusable wrapper
In real projects, create a wrapper function that centralizes common logic: base URL, default headers, error handling, and serialization.
Practice
- Make a GET with validation: Write an async function
fetchData(url)that performs afetchGET, checksresponse.ok, and returns the data parsed as JSON. Handle HTTP errors by throwing anErrorwith the status code. - Send data with POST: Create a function
submitForm(url, data)that performs afetchPOST withContent-Type: application/jsonand the body serialized withJSON.stringify. Check the response and return the result. - Build a URL with parameters: Use
URLSearchParamsto build a URL with the parameterssearch,page, andlimit. Print the complete URL to the console.
In the next lesson we will learn about ES modules to organize our code.
// Basic GET
async function getUsers() {
const response = await fetch('https://api.example.com/users');
// Check HTTP status
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const users = await response.json();
return users;
}
// POST - send data
async function createUser(data) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return response.json();
}
// PUT - update
async function updateUser(id, data) {
return fetch(`https://api.example.com/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
}
// DELETE
async function deleteUser(id) {
const res = await fetch(`https://api.example.com/users/${id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error('Could not delete');
}
// Usage
const created = await createUser({
name: 'Maria',
email: '[email protected]',
});
console.log('Created:', created);
// AbortController - cancel requests
const controller = new AbortController();
async function search(term) {
try {
const res = await fetch(`/api/search?q=${term}`, {
signal: controller.signal,
});
return res.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request cancelled');
} else {
throw error;
}
}
}
// Cancel the request
controller.abort();
// Query parameters with URLSearchParams
function buildURL(base, params) {
const url = new URL(base);
const searchParams = new URLSearchParams(params);
url.search = searchParams.toString();
return url.toString();
}
const url = buildURL('https://api.example.com/products', {
category: 'tech',
sort: 'price',
page: '1',
limit: '20',
});
// https://api.example.com/products?category=tech&sort=price&page=1&limit=20
// Reusable wrapper
async function api(endpoint, options = {}) {
const config = {
headers: { 'Content-Type': 'application/json' },
...options,
body: options.body ? JSON.stringify(options.body) : undefined,
};
const response = await fetch(`/api${endpoint}`, config);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}
// Using the wrapper
const users = await api('/users');
const post = await api('/posts', {
method: 'POST',
body: { title: 'New post', content: '...' },
});
Sign in to track your progress