Networking
The fetch API
The Fetch API is the modern way to make HTTP requests in the browser. It uses promises, making it clean and easy to work with.
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
});
Making get requests
The simplest use of fetch is getting data:
fetch('https://api.example.com/users')
.then(response => response.json())
.then(users => {
console.log(users);
// Display users in UI
});
Handling the response
The fetch promise resolves with a Response object:
fetch('https://api.example.com/data')
.then(response => {
console.log(response.status); // 200, 404, etc.
console.log(response.ok); // true if status 200-299
console.log(response.headers); // Response headers
return response.json(); // Parse as JSON
})
.then(data => {
console.log(data);
});
Checking for errors
fetch only rejects on network errors, not HTTP errors:
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
Handling JSON
Most APIs return JSON. Parse it with .json():
fetch('https://api.example.com/users')
.then(response => response.json()) // Parse JSON
.then(data => {
// data is a JavaScript object
data.forEach(user => {
console.log(user.name);
});
});
Other response types
// Text
fetch('/api/data.txt')
.then(response => response.text())
.then(text => console.log(text));
// Blob (for images, files)
fetch('/api/image.jpg')
.then(response => response.blob())
.then(blob => {
const imageUrl = URL.createObjectURL(blob);
img.src = imageUrl;
});
Error handling
Always handle errors:
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.then(data => {
// Handle success
displayData(data);
})
.catch(error => {
// Handle errors
console.error('Fetch failed:', error);
showError('Failed to load data');
});
Using async/await
fetch works great with async/await:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
displayData(data);
} catch (error) {
console.error('Error:', error);
showError('Failed to load data');
}
}
Loading states
Show loading indicators while fetching:
async function loadUsers() {
const loadingDiv = document.querySelector('#loading');
const errorDiv = document.querySelector('#error');
const usersDiv = document.querySelector('#users');
// Show loading
loadingDiv.style.display = 'block';
errorDiv.style.display = 'none';
usersDiv.innerHTML = '';
try {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Failed to load');
const users = await response.json();
// Hide loading, show data
loadingDiv.style.display = 'none';
displayUsers(users);
} catch (error) {
// Hide loading, show error
loadingDiv.style.display = 'none';
errorDiv.style.display = 'block';
errorDiv.textContent = 'Failed to load users';
}
}
Post requests
Send data to the server:
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Alice',
email: 'alice@example.com'
})
})
.then(response => response.json())
.then(data => {
console.log('User created:', data);
});
Sending form data
Learn more about form handling in the events guide.
const form = document.querySelector('form');
form.addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(form);
const response = await fetch('/api/submit', {
method: 'POST',
body: formData // FormData works directly
});
const result = await response.json();
console.log('Submitted:', result);
});
Other HTTP methods
// PUT - update
fetch('/api/users/123', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Bob' })
});
// DELETE
fetch('/api/users/123', {
method: 'DELETE'
});
// PATCH - partial update
fetch('/api/users/123', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Charlie' })
});
Basic security considerations
CORS (Cross-Origin Resource Sharing)
Browsers block requests to different origins (different domain, port, or protocol) unless the server allows it:
// This will fail if api.example.com doesn't allow CORS
fetch('https://api.example.com/data')
.catch(error => {
console.error('CORS error:', error);
});
Solutions:
- Server must send CORS headers
- Use a proxy server
- Make requests to the same origin
Same-origin policy
Requests to the same origin work without CORS:
// Same origin - works fine
fetch('/api/data') // Same domain, protocol, port
.then(response => response.json());
Avoiding XSS
Never trust data from APIs without validation:
// Dangerous: directly inserting API data
fetch('/api/comments')
.then(response => response.json())
.then(comments => {
comments.forEach(comment => {
div.innerHTML += `<p>${comment.text}</p>`; // XSS risk!
});
});
// Safe: use textContent
fetch('/api/comments')
.then(response => response.json())
.then(comments => {
comments.forEach(comment => {
const p = document.createElement('p');
p.textContent = comment.text; // Safe
div.appendChild(p);
});
});
Common patterns
Reusable fetch function
async function apiRequest(url, options = {}) {
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
// Usage
const users = await apiRequest('/api/users');
const user = await apiRequest('/api/users/123', {
method: 'PUT',
body: JSON.stringify({ name: 'Alice' })
});
Handling multiple requests
// Sequential
const user = await fetch('/api/user/123').then(r => r.json());
const posts = await fetch('/api/user/123/posts').then(r => r.json());
// Parallel (faster)
const [user, posts] = await Promise.all([
fetch('/api/user/123').then(r => r.json()),
fetch('/api/user/123/posts').then(r => r.json())
]);
Learn more about Promise.all() in the promises guide.
### Retry logic
```javascript
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (response.ok) {
return await response.json();
}
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}