Modern web applications communicate with servers through HTTP requests— fetching data, submitting forms, updating resources, and more. The Fetch API is JavaScript's modern, Promise-based interface for making HTTP requests. It replaces the older XMLHttpRequest with a cleaner, more powerful API. Whether you're calling REST APIs, loading data, or building full-stack applications, mastering fetch() is essential. Let's learn to fetch like a pro!
What is the Fetch API?
Fetch API Features:
- Modern replacement for XMLHttpRequest
- Returns Promises (works great with async/await)
- Supports all HTTP methods (GET, POST, PUT, DELETE, etc.)
- Handles different response types (JSON, text, blob)
- Built into modern browsers (no library needed)
- Flexible configuration (headers, body, credentials)
Basic GET Request
// Basic fetch - GET request (default)
fetch('https://api.example.com/users')
.then(response => response.json()) // Parse JSON
.then(data => {
console.log('Users:', data);
})
.catch(error => {
console.error('Error:', error);
});
// With async/await (cleaner!)
async function getUsers() {
try {
let response = await fetch('https://api.example.com/users');
let data = await response.json();
console.log('Users:', data);
return data;
} catch (error) {
console.error('Error:', error);
return null;
}
}
// Call the function
let users = await getUsers();
// Real-world example: Load posts
async function loadPosts() {
try {
let response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let posts = await response.json();
return posts;
} catch (error) {
console.error('Failed to load posts:', error);
return [];
}
}
let posts = await loadPosts();
console.log(`Loaded ${posts.length} posts`);Understanding the Response Object
async function exploreResponse() {
let response = await fetch('https://api.example.com/users');
// Response properties
console.log('Status:', response.status); // 200, 404, 500, etc.
console.log('Status Text:', response.statusText); // "OK", "Not Found"
console.log('OK:', response.ok); // true if 200-299
console.log('URL:', response.url); // Final URL
console.log('Headers:', response.headers); // Headers object
// Response methods (each can only be called once!)
let json = await response.json(); // Parse as JSON
// let text = await response.text(); // Get as text
// let blob = await response.blob(); // Get as binary data
// let buffer = await response.arrayBuffer(); // Get as ArrayBuffer
return json;
}
// Check response status
async function getUserById(id) {
let response = await fetch(`https://api.example.com/users/${id}`);
if (response.ok) {
// Status 200-299
let user = await response.json();
return user;
} else if (response.status === 404) {
console.log('User not found');
return null;
} else if (response.status === 500) {
console.log('Server error');
return null;
} else {
console.log(`Unexpected status: ${response.status}`);
return null;
}
}
// Read headers
async function checkHeaders() {
let response = await fetch('https://api.example.com/users');
console.log('Content-Type:', response.headers.get('Content-Type'));
console.log('Date:', response.headers.get('Date'));
// Check if JSON
let contentType = response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
let data = await response.json();
return data;
} else {
let text = await response.text();
return text;
}
}HTTP Methods (CRUD Operations)
GET - Retrieve Data
// GET request (default method)
async function getUser(userId) {
let response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
let user = await response.json();
return user;
}
// GET with query parameters
async function searchUsers(query) {
// Build URL with query parameters
let params = new URLSearchParams({
q: query,
limit: 10,
page: 1
});
let response = await fetch(`https://api.example.com/users?${params}`);
let results = await response.json();
return results;
}
// Usage
let user = await getUser(123);
let results = await searchUsers('alice');POST - Create Data
// POST request - create new resource
async function createUser(userData) {
try {
let response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let newUser = await response.json();
console.log('User created:', newUser);
return newUser;
} catch (error) {
console.error('Failed to create user:', error);
throw error;
}
}
// Usage
let newUser = await createUser({
name: 'Alice',
email: 'alice@example.com',
age: 25
});
// POST with form data
async function uploadFile(file) {
let formData = new FormData();
formData.append('file', file);
formData.append('description', 'My file');
let response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData // Don't set Content-Type header with FormData!
});
let result = await response.json();
return result;
}PUT/PATCH - Update Data
// PUT - replace entire resource
async function updateUser(userId, userData) {
let response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error('Failed to update user');
}
let updatedUser = await response.json();
return updatedUser;
}
// PATCH - update specific fields
async function patchUser(userId, updates) {
let response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});
let updatedUser = await response.json();
return updatedUser;
}
// Usage
// PUT - replace entire user
await updateUser(123, {
name: 'Alice Smith',
email: 'alice@example.com',
age: 26
});
// PATCH - update only age
await patchUser(123, {
age: 26
});DELETE - Remove Data
// DELETE request
async function deleteUser(userId) {
try {
let response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('Failed to delete user');
}
// Some APIs return no content (204)
if (response.status === 204) {
return true;
}
// Others return confirmation
let result = await response.json();
return result;
} catch (error) {
console.error('Delete failed:', error);
return false;
}
}
// Usage
let success = await deleteUser(123);
if (success) {
console.log('User deleted');
}Request Configuration
Headers
// Setting request headers
async function fetchWithAuth() {
let response = await fetch('https://api.example.com/protected', {
headers: {
'Authorization': 'Bearer YOUR_TOKEN_HERE',
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Custom-Header': 'custom-value'
}
});
let data = await response.json();
return data;
}
// Headers object (alternative)
let headers = new Headers();
headers.append('Authorization', 'Bearer TOKEN');
headers.append('Content-Type', 'application/json');
let response = await fetch('https://api.example.com/data', { headers });
// Dynamic headers
function getHeaders(includeAuth = true) {
let headers = {
'Content-Type': 'application/json'
};
if (includeAuth) {
let token = localStorage.getItem('authToken');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
}
return headers;
}
// Usage
let response = await fetch('/api/users', {
headers: getHeaders()
});Request Options
// Complete request configuration
async function configuredFetch() {
let response = await fetch('https://api.example.com/data', {
method: 'POST', // HTTP method
headers: { // Request headers
'Content-Type': 'application/json'
},
body: JSON.stringify({ // Request body
name: 'Alice'
}),
mode: 'cors', // cors, no-cors, same-origin
credentials: 'include', // include, same-origin, omit
cache: 'no-cache', // default, no-cache, reload, force-cache
redirect: 'follow', // follow, error, manual
referrerPolicy: 'no-referrer' // Referrer policy
});
return response.json();
}
// Credentials (cookies)
// 'include' - send cookies even cross-origin
// 'same-origin' - send cookies only same-origin (default)
// 'omit' - never send cookies
async function fetchWithCookies() {
let response = await fetch('https://api.example.com/user', {
credentials: 'include' // Include cookies
});
return response.json();
}Error Handling
// Comprehensive error handling
async function robustFetch(url, options = {}) {
try {
let response = await fetch(url, options);
// Check if response is OK (200-299)
if (!response.ok) {
// Try to get error message from response
let errorData;
try {
errorData = await response.json();
} catch {
errorData = { message: response.statusText };
}
throw new Error(
`HTTP ${response.status}: ${errorData.message || 'Request failed'}`
);
}
// Parse response based on content type
let contentType = response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else if (contentType && contentType.includes('text/')) {
return await response.text();
} else {
return await response.blob();
}
} catch (error) {
// Network errors, timeouts, etc.
if (error.name === 'TypeError') {
console.error('Network error:', error);
throw new Error('Network error. Check your connection.');
}
// HTTP errors
if (error.message.startsWith('HTTP')) {
console.error('HTTP error:', error);
throw error;
}
// Other errors
console.error('Unexpected error:', error);
throw new Error('An unexpected error occurred.');
}
}
// Usage with specific error handling
async function loadUserData(userId) {
try {
let user = await robustFetch(`/api/users/${userId}`);
return user;
} catch (error) {
if (error.message.includes('404')) {
alert('User not found');
} else if (error.message.includes('500')) {
alert('Server error. Please try again later.');
} else if (error.message.includes('Network')) {
alert('No internet connection');
} else {
alert('Failed to load user');
}
return null;
}
}
// Timeout wrapper
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
let controller = new AbortController();
let timeoutId = setTimeout(() => controller.abort(), timeout);
try {
let response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
// Usage
try {
let response = await fetchWithTimeout('/api/data', {}, 3000);
let data = await response.json();
} catch (error) {
console.error(error.message);
}Practical Examples
Example 1: User Management API
// Complete User API client
class UserAPI {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
let url = `${this.baseURL}${endpoint}`;
let config = {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
};
try {
let response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
}
// GET all users
async getUsers() {
return this.request('/users');
}
// GET single user
async getUser(id) {
return this.request(`/users/${id}`);
}
// POST create user
async createUser(userData) {
return this.request('/users', {
method: 'POST',
body: JSON.stringify(userData)
});
}
// PUT update user
async updateUser(id, userData) {
return this.request(`/users/${id}`, {
method: 'PUT',
body: JSON.stringify(userData)
});
}
// DELETE user
async deleteUser(id) {
return this.request(`/users/${id}`, {
method: 'DELETE'
});
}
// Search users
async searchUsers(query) {
let params = new URLSearchParams({ q: query });
return this.request(`/users/search?${params}`);
}
}
// Usage
let api = new UserAPI('https://api.example.com');
// Get all users
let users = await api.getUsers();
// Create user
let newUser = await api.createUser({
name: 'Alice',
email: 'alice@example.com'
});
// Update user
await api.updateUser(newUser.id, {
name: 'Alice Smith'
});
// Delete user
await api.deleteUser(newUser.id);
// Search
let results = await api.searchUsers('alice');Example 2: Loading Indicators
// Fetch with loading state
class DataLoader {
constructor() {
this.loading = false;
}
async fetchWithLoading(url, options = {}) {
this.showLoading();
try {
let response = await fetch(url, options);
if (!response.ok) {
throw new Error('Request failed');
}
let data = await response.json();
this.hideLoading();
return data;
} catch (error) {
this.hideLoading();
this.showError(error.message);
throw error;
}
}
showLoading() {
this.loading = true;
let loader = document.getElementById('loader');
if (loader) {
loader.style.display = 'block';
}
}
hideLoading() {
this.loading = false;
let loader = document.getElementById('loader');
if (loader) {
loader.style.display = 'none';
}
}
showError(message) {
alert(`Error: ${message}`);
}
}
// Usage
let loader = new DataLoader();
async function loadData() {
try {
let data = await loader.fetchWithLoading('/api/data');
displayData(data);
} catch (error) {
console.error('Failed to load data');
}
}Example 3: Parallel Requests
// Load multiple resources in parallel
async function loadDashboard(userId) {
try {
// Start all requests simultaneously
let [user, posts, comments, stats] = await Promise.all([
fetch(`/api/users/${userId}`).then(r => r.json()),
fetch(`/api/posts?userId=${userId}`).then(r => r.json()),
fetch(`/api/comments?userId=${userId}`).then(r => r.json()),
fetch(`/api/stats?userId=${userId}`).then(r => r.json())
]);
return { user, posts, comments, stats };
} catch (error) {
console.error('Failed to load dashboard:', error);
throw error;
}
}
// Usage
let dashboard = await loadDashboard(123);
console.log('User:', dashboard.user.name);
console.log('Posts:', dashboard.posts.length);
// Handle partial failures with Promise.allSettled
async function loadDashboardSafe(userId) {
let results = await Promise.allSettled([
fetch(`/api/users/${userId}`).then(r => r.json()),
fetch(`/api/posts?userId=${userId}`).then(r => r.json()),
fetch(`/api/comments?userId=${userId}`).then(r => r.json())
]);
let data = {};
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
let keys = ['user', 'posts', 'comments'];
data[keys[index]] = result.value;
} else {
console.error(`Failed to load ${['user', 'posts', 'comments'][index]}`);
}
});
return data;
}Fetch API and HTTP Requests
Master API calls
console.log() to see your output in the console above.Key Takeaways
fetch()makes HTTP requests and returns a Promise- Response object has status, ok, headers, and parsing methods
response.json()parses JSON (also returns Promise)- GET retrieves, POST creates, PUT/PATCH updates, DELETE removes
- Set method, headers, and body in options object
- Always check
response.okbefore parsing - Use try-catch for network and HTTP errors
- Headers object for authentication, content-type, etc.
Promise.all()for parallel requests- Works best with async/await for clean code
What's Next?
You now understand the Fetch API and can make HTTP requests to servers! You've learned GET, POST, PUT, DELETE requests, error handling, and practical patterns for building API-connected applications.
In the next lesson, we'll explore Working with Local Storage—learning to store and retrieve data in the browser, creating persistent user experiences!
💪 Practice Challenge:
Before moving on, try:
- Make a GET request to a public API
- Create a POST request with JSON body
- Build a user API client with all CRUD operations
- Add proper error handling to all requests
- Load multiple resources in parallel with Promise.all()