Beginner
30 min
Full Guide
Fetch API & HTTP Requests
Master the Fetch API for making HTTP requests in JavaScript with practical examples
What is the Fetch API?
The Fetch API is a modern, promise-based interface for making HTTP requests in JavaScript. It's built into all modern browsers and provides a cleaner, more powerful alternative to the older XMLHttpRequest (XHR) approach.
Fetch is native to JavaScript—no external libraries required—and works seamlessly with async/await syntax.
✨ Fetch API Advantages
🎯
Promise-Based
Clean async/await syntax
📦
Native Support
No external dependencies
🔄
Streaming
Stream response body
🛡️
CORS Support
Built-in cross-origin handling
Basic GET Request
// Simple GET request
async function fetchUsers() {
try {
const response = await fetch('https://api.example.com/users');
// Check if request was successful
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
// Parse JSON response
const users = await response.json();
console.log('Users:', users);
return users;
} catch (error) {
console.error('Fetch error:', error);
}
}
// With query parameters
async function searchUsers(query, page = 1) {
const params = new URLSearchParams({
q: query,
page: page,
limit: 10
});
const response = await fetch(`https://api.example.com/users?${params}`);
return response.json();
}
// Using URL constructor
async function getUserById(id) {
const url = new URL(`https://api.example.com/users/${id}`);
url.searchParams.set('include', 'posts');
const response = await fetch(url);
return response.json();
}
POST Request - Creating Data
// POST request with JSON body
async function createUser(userData) {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to create user');
}
const newUser = await response.json();
console.log('Created user:', newUser);
return newUser;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
}
// Usage
createUser({
name: 'John Doe',
email: 'john@example.com',
role: 'admin'
});
// POST with form data
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'Profile picture');
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
// Don't set Content-Type for FormData - browser sets it automatically
body: formData
});
return response.json();
}
PUT, PATCH, and DELETE Requests
// PUT - Replace entire resource
async function updateUser(id, userData) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error('Failed to update user');
}
return response.json();
}
// PATCH - Partial update
async function patchUser(id, updates) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token'
},
body: JSON.stringify(updates)
});
return response.json();
}
// Usage: Only update specific fields
patchUser(123, { status: 'active' });
// DELETE - Remove resource
async function deleteUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'DELETE',
headers: {
'Authorization': 'Bearer your-token'
}
});
if (!response.ok) {
throw new Error('Failed to delete user');
}
// DELETE often returns 204 No Content
if (response.status === 204) {
return { success: true };
}
return response.json();
}
Request Configuration Options
// Full fetch options reference
const response = await fetch(url, {
// HTTP method
method: 'POST',
// Request headers
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token',
'Accept': 'application/json',
'X-Custom-Header': 'value'
},
// Request body (for POST, PUT, PATCH)
body: JSON.stringify(data),
// Request mode
mode: 'cors', // cors, no-cors, same-origin
// Credentials inclusion
credentials: 'include', // include, same-origin, omit
// Cache mode
cache: 'default', // default, no-store, reload, no-cache, force-cache
// Redirect behavior
redirect: 'follow', // follow, error, manual
// Referrer policy
referrerPolicy: 'no-referrer-when-downgrade',
// AbortController signal for cancellation
signal: controller.signal,
// Keep connection alive after page unload
keepalive: false
});
Response Handling
// Response object properties and methods
async function handleResponse() {
const response = await fetch('https://api.example.com/data');
// Response properties
console.log('Status:', response.status); // 200
console.log('Status Text:', response.statusText); // "OK"
console.log('OK?:', response.ok); // true (if 200-299)
console.log('Headers:', response.headers);
console.log('URL:', response.url);
console.log('Type:', response.type); // "cors", "basic", etc.
console.log('Redirected:', response.redirected);
// Reading headers
const contentType = response.headers.get('Content-Type');
const rateLimit = response.headers.get('X-RateLimit-Remaining');
// Iterating headers
for (const [key, value] of response.headers) {
console.log(`${key}: ${value}`);
}
// Response body methods (can only be read ONCE!)
// Choose the appropriate one based on content type:
// For JSON
const json = await response.json();
// For plain text
const text = await response.text();
// For binary data (images, files)
const blob = await response.blob();
// For binary as ArrayBuffer
const buffer = await response.arrayBuffer();
// For form data
const formData = await response.formData();
}
// Clone response to read body multiple times
async function readMultipleTimes() {
const response = await fetch(url);
// Clone before reading
const clone = response.clone();
const text = await response.text();
const json = await clone.json(); // Can still read clone
}
Error Handling
// Comprehensive error handling
async function fetchWithErrorHandling(url, options = {}) {
try {
const response = await fetch(url, options);
// Fetch only rejects on network errors, not HTTP errors!
// We need to check response.ok manually
if (!response.ok) {
// Try to get error details from response body
let errorMessage = `HTTP error! Status: ${response.status}`;
try {
const errorData = await response.json();
errorMessage = errorData.message || errorMessage;
} catch {
// Response body wasn't JSON
}
// Create custom error with details
const error = new Error(errorMessage);
error.status = response.status;
error.statusText = response.statusText;
throw error;
}
return await response.json();
} catch (error) {
// Network errors (no internet, DNS failure, etc.)
if (error.name === 'TypeError') {
console.error('Network error:', error.message);
throw new Error('Unable to connect to server');
}
// Abort errors
if (error.name === 'AbortError') {
console.error('Request was cancelled');
throw new Error('Request cancelled');
}
// Re-throw other errors
throw error;
}
}
// Usage with specific error handling
async function loadData() {
try {
const data = await fetchWithErrorHandling('/api/data');
console.log('Data loaded:', data);
} catch (error) {
if (error.status === 401) {
// Redirect to login
window.location.href = '/login';
} else if (error.status === 404) {
// Show not found message
showNotFound();
} else {
// Generic error
showError(error.message);
}
}
}
Request Cancellation with AbortController
// Cancelling requests with AbortController
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}
// Cancel on user action
class SearchController {
constructor() {
this.controller = null;
}
async search(query) {
// Cancel previous request
if (this.controller) {
this.controller.abort();
}
// Create new controller for this request
this.controller = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: this.controller.signal
});
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Previous search cancelled');
return null;
}
throw error;
}
}
}
// Usage with search input
const searchController = new SearchController();
searchInput.addEventListener('input', async (e) => {
const results = await searchController.search(e.target.value);
if (results) {
displayResults(results);
}
});
Parallel and Sequential Requests
// Parallel requests with Promise.all
async function fetchParallel() {
try {
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())
]);
console.log('Users:', users);
console.log('Posts:', posts);
console.log('Comments:', comments);
return { users, posts, comments };
} catch (error) {
// One failure = all fail
console.error('One request failed:', error);
}
}
// Parallel with partial failure handling
async function fetchWithPartialSuccess() {
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return results.map((result, index) => {
if (result.status === 'fulfilled') {
return { success: true, data: result.value };
}
return { success: false, error: result.reason };
});
}
// Sequential requests (when order matters)
async function fetchSequential() {
// Get user first
const userResponse = await fetch('/api/users/1');
const user = await userResponse.json();
// Then get user's posts
const postsResponse = await fetch(`/api/users/${user.id}/posts`);
const posts = await postsResponse.json();
// Then get comments for each post
const postsWithComments = await Promise.all(
posts.map(async (post) => {
const commentsResponse = await fetch(`/api/posts/${post.id}/comments`);
const comments = await commentsResponse.json();
return { ...post, comments };
})
);
return { user, posts: postsWithComments };
}
Reusable Fetch Wrapper
// Production-ready fetch wrapper
class HttpClient {
constructor(baseUrl, defaultOptions = {}) {
this.baseUrl = baseUrl;
this.defaultOptions = {
headers: {
'Content-Type': 'application/json'
},
...defaultOptions
};
}
setAuthToken(token) {
this.defaultOptions.headers['Authorization'] = `Bearer ${token}`;
}
removeAuthToken() {
delete this.defaultOptions.headers['Authorization'];
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const mergedOptions = {
...this.defaultOptions,
...options,
headers: {
...this.defaultOptions.headers,
...options.headers
}
};
if (options.body && typeof options.body === 'object') {
mergedOptions.body = JSON.stringify(options.body);
}
const response = await fetch(url, mergedOptions);
if (!response.ok) {
const error = new Error('Request failed');
error.status = response.status;
try {
error.data = await response.json();
} catch {}
throw error;
}
if (response.status === 204) {
return null;
}
return response.json();
}
get(endpoint, options) {
return this.request(endpoint, { ...options, method: 'GET' });
}
post(endpoint, body, options) {
return this.request(endpoint, { ...options, method: 'POST', body });
}
put(endpoint, body, options) {
return this.request(endpoint, { ...options, method: 'PUT', body });
}
patch(endpoint, body, options) {
return this.request(endpoint, { ...options, method: 'PATCH', body });
}
delete(endpoint, options) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
}
// Usage
const api = new HttpClient('https://api.example.com/v1');
api.setAuthToken('jwt-token-here');
// Make requests
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John', email: 'john@test.com' });
await api.patch(`/users/${newUser.id}`, { status: 'active' });
await api.delete(`/users/${newUser.id}`);
💡 Fetch API Best Practices
- ✓ Always check response.ok - Fetch doesn't reject on HTTP errors
- ✓ Use try/catch - Handle network errors and HTTP errors separately
- ✓ Set appropriate headers - Content-Type, Authorization, etc.
- ✓ Use AbortController - Cancel stale requests
- ✓ Create reusable wrappers - DRY code with consistent error handling
- ✓ Use URLSearchParams - For query string construction