Back to Blog
Tutorial2024-12-31

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