The Complete Fetch API Guide: From Basics to Advanced Patterns
Master the Fetch API for making HTTP requests. Learn GET, POST, error handling, headers, and real-world patterns.
What is the Fetch API?
The Fetch API is the modern way to make HTTP requests in JavaScript. It replaced XMLHttpRequest (XHR) with a cleaner, Promise-based interface.
``javascript
// Basic GET request
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
`
Basic GET Request
`javascript
// With async/await
async function getUser(id) {
const response = await fetch(https://api.example.com/users/${id});
const user = await response.json();
return user;
}
// With .then()
fetch('https://api.example.com/users/1')
.then(response => response.json())
.then(user => console.log(user));
`
POST Request with JSON Body
`javascript
async function createUser(userData) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
return response.json(); }
// Usage
const newUser = await createUser({
name: 'Alice',
email: 'alice@example.com'
});
`
Error Handling (The Right Way)
Important: fetch() only rejects on network failures, NOT on HTTP errors (4xx, 5xx)!
`javascript
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url);
// Check if response is OK (status 200-299)
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status});
}
return await response.json();
} catch (error) {
if (error.name === 'TypeError') {
// Network error or CORS issue
console.error('Network error:', error);
} else {
// HTTP error or JSON parsing error
console.error('Error:', error);
}
throw error;
}
}
`
All HTTP Methods
`javascript
// GET (default)
fetch('/api/users');
// POST fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Alice' }) });
// PUT (full update) fetch('/api/users/1', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Alice', email: 'new@email.com' }) });
// PATCH (partial update) fetch('/api/users/1', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Alice Updated' }) });
// DELETE
fetch('/api/users/1', { method: 'DELETE' });
`
Working with Headers
`javascript
// Setting headers
const response = await fetch('/api/data', {
headers: {
'Authorization': 'Bearer token123',
'Content-Type': 'application/json',
'X-Custom-Header': 'custom-value'
}
});
// Reading response headers console.log(response.headers.get('Content-Type')); console.log(response.headers.get('X-RateLimit-Remaining'));
// Using Headers object
const headers = new Headers();
headers.append('Authorization', 'Bearer token');
fetch('/api/data', { headers });
`
Response Types
`javascript
const response = await fetch(url);
// JSON const json = await response.json();
// Text const text = await response.text();
// Blob (for files/images) const blob = await response.blob(); const imageUrl = URL.createObjectURL(blob);
// ArrayBuffer (for binary data) const buffer = await response.arrayBuffer();
// FormData
const formData = await response.formData();
`
Sending Form Data
`javascript
// URL-encoded form data
const formData = new URLSearchParams();
formData.append('username', 'alice');
formData.append('password', 'secret');
await fetch('/login', { method: 'POST', body: formData, });
// Multipart form data (for file uploads) const form = new FormData(); form.append('file', fileInput.files[0]); form.append('description', 'My file');
await fetch('/upload', {
method: 'POST',
body: form, // Don't set Content-Type header - browser sets it with boundary
});
`
Timeout with AbortController
`javascript
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(id);
return response;
} catch (error) {
clearTimeout(id);
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}
`
Cancel Requests
`javascript
const controller = new AbortController();
// Start the fetch const fetchPromise = fetch('/api/data', { signal: controller.signal });
// Cancel it anytime controller.abort();
// Handle cancellation
try {
const response = await fetchPromise;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch was cancelled');
}
}
`
Creating a Fetch Wrapper
`javascript
class API {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) { const url = this.baseURL + endpoint;
const config = { ...options, headers: { 'Content-Type': 'application/json', ...options.headers, }, };
if (options.body) { config.body = JSON.stringify(options.body); }
const response = await fetch(url, config);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || HTTP ${response.status});
}
return response.json(); }
get(endpoint) { return this.request(endpoint); }
post(endpoint, body) { return this.request(endpoint, { method: 'POST', body }); }
put(endpoint, body) { return this.request(endpoint, { method: 'PUT', body }); }
delete(endpoint) { return this.request(endpoint, { method: 'DELETE' }); } }
// Usage const api = new API('https://api.example.com');
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'Alice' });
await api.delete('/users/1');
`
Common Patterns
Retry on Failure
`javascript
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) return response;
} catch (error) {
if (i === retries - 1) throw error;
}
await new Promise(r => setTimeout(r, 1000 * (i + 1))); // Exponential backoff
}
}
`
Parallel Requests
`javascript
// Fetch multiple resources in parallel
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()),
]);
`
Summary
- Fetch is Promise-based and cleaner than XHR
- Always check response.ok - fetch doesn't throw on HTTP errors
- Use AbortController for timeouts and cancellation
- Set Content-Type header for JSON requests
- Don't set Content-Type` for FormData (browser does it)
- Create wrapper functions for cleaner code