Learning Objectives
- Make GET and POST requests with fetch
- Handle responses and errors properly
- Send JSON data and custom headers
- Use async/await with fetch
- Apply fetch best practices
Basic GET Request
// Simple GET request with Promises
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => {
console.log('Users:', data);
})
.catch(error => {
console.error('Error:', error);
});
// With async/await (recommended)
async function getUsers() {
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
console.log('Users:', data);
return data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// Usage
const users = await getUsers();
POST Request
async function createUser(userData) {
const 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}`);
}
const data = await response.json();
return data;
}
// Usage
const newUser = await createUser({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
console.log('Created user:', newUser);
Error Handling
Important: Fetch only rejects on network errors, not HTTP errors (404, 500, etc.)!
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 ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
// Network error
if (error.name === 'TypeError') {
console.error('Network error:', error.message);
}
// HTTP error
else {
console.error('HTTP error:', error.message);
}
throw error;
}
}
// Usage
try {
const data = await fetchWithErrorHandling('https://api.example.com/data');
} catch (error) {
// Handle error in UI
showErrorMessage(error.message);
}
Request Options
const response = await fetch(url, {
// HTTP method
method: 'POST', // GET, POST, PUT, DELETE, PATCH, etc.
// Request headers
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-token-here',
'Accept': 'application/json'
},
// Request body (for POST, PUT, PATCH)
body: JSON.stringify({
key: 'value'
}),
// CORS mode
mode: 'cors', // cors, no-cors, same-origin
// Credentials (cookies)
credentials: 'include', // include, same-origin, omit
// Cache mode
cache: 'no-cache', // default, no-cache, reload, force-cache, only-if-cached
// Redirect handling
redirect: 'follow', // follow, error, manual
// Referrer policy
referrerPolicy: 'no-referrer',
// Abort signal (for cancellation)
signal: abortController.signal
});
Response Methods
const response = await fetch(url);
// Parse response body
const json = await response.json(); // Parse as JSON
const text = await response.text(); // Get as plain text
const blob = await response.blob(); // Get as Blob (files)
const formData = await response.formData(); // Parse as FormData
const arrayBuffer = await response.arrayBuffer(); // Get as ArrayBuffer
// Response properties
console.log(response.ok); // true if status 200-299
console.log(response.status); // HTTP status code (200, 404, etc.)
console.log(response.statusText); // Status message ("OK", "Not Found")
console.log(response.headers); // Headers object
console.log(response.url); // Final URL (after redirects)
console.log(response.redirected); // Was redirected?
console.log(response.type); // Response type (basic, cors, etc.)
// Get specific header
const contentType = response.headers.get('Content-Type');
Real-World Examples
API Client Class
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.token = null;
}
setToken(token) {
this.token = token;
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const headers = {
'Content-Type': 'application/json',
...options.headers
};
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
const response = await fetch(url, {
...options,
headers
});
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, { method: 'GET' });
}
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
}
// Usage
const api = new ApiClient('https://api.example.com');
api.setToken('my-auth-token');
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John' });
await api.delete('/users/123');
Request Timeout
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const 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 {
const response = await fetchWithTimeout('https://api.example.com/slow', {}, 3000);
const data = await response.json();
} catch (error) {
console.error('Request failed:', error.message);
}
Retry Logic
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;
}
// Don't retry client errors (4xx)
if (response.status >= 400 && response.status < 500) {
throw new Error(`HTTP ${response.status}`);
}
// Retry server errors (5xx)
if (i < retries - 1) {
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
continue;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
// Usage
const response = await fetchWithRetry('https://api.example.com/data');
const data = await response.json();
Best Practices
1. Always check response.ok
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
2. Use async/await for cleaner code
// Good
const data = await fetch(url).then(r => r.json());
// Better
const response = await fetch(url);
const data = await response.json();
3. Handle errors properly
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
} catch (error) {
console.error('Fetch failed:', error);
}
4. Set appropriate headers
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
Key Takeaways
- Fetch returns Promises - use async/await
- Always check
response.okfor HTTP errors - Fetch only rejects on network errors, not HTTP errors
- Parse response with
.json(),.text(), etc. - Set headers and options for custom requests
- Use AbortController for request cancellation
- Implement retry logic for failed requests
- Handle both network and HTTP errors