Fetch API
Learning Objectives
- By the end of this lesson, you will be able to:
- - Make HTTP requests using the Fetch API
- - Perform GET, POST, PUT, and DELETE requests
- - Configure request options (headers, body, method)
- - Handle responses and parse data
- - Handle errors properly
- - Work with async/await and Fetch
- - Build API-integrated applications
Lesson 12.1: Fetch API
Learning Objectives
By the end of this lesson, you will be able to:
- Make HTTP requests using the Fetch API
- Perform GET, POST, PUT, and DELETE requests
- Configure request options (headers, body, method)
- Handle responses and parse data
- Handle errors properly
- Work with async/await and Fetch
- Build API-integrated applications
Introduction to Fetch API
The Fetch API provides a modern, promise-based way to make HTTP requests. It's built into modern browsers and is the standard for making network requests in JavaScript.
Why Fetch API?
- Promise-based: Works with async/await
- Modern: Built into browsers
- Flexible: Supports all HTTP methods
- Streaming: Can handle streaming responses
- Standard: Replaces older XMLHttpRequest
Basic Fetch Request
Simple GET Request
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
Fetch with async/await
async function fetchUsers() {
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
fetchUsers();
HTTP Methods
GET Request
// Simple GET
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data));
// GET with async/await
async function getUsers() {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
return data;
}
POST Request
// POST with JSON body
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('Created:', data));
// POST with async/await
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 await response.json();
}
PUT Request
// PUT to update resource
async function updateUser(userId, userData) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
return await response.json();
}
DELETE Request
// DELETE to remove resource
async function deleteUser(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'DELETE'
});
return await response.json();
}
Request Options
Headers
fetch('https://api.example.com/data', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123',
'X-Custom-Header': 'value'
}
});
Request Body
// JSON body
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Alice',
age: 25
})
});
// Form data
const formData = new FormData();
formData.append('name', 'Alice');
formData.append('age', '25');
fetch('https://api.example.com/users', {
method: 'POST',
body: formData
});
Complete Request Options
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: 'Alice',
email: 'alice@example.com'
}),
mode: 'cors', // cors, no-cors, same-origin
cache: 'no-cache', // default, no-cache, reload, force-cache
credentials: 'include', // include, same-origin, omit
redirect: 'follow' // follow, error, manual
});
Response Handling
Checking Response Status
async function fetchData() {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
}
Response Methods
async function handleResponse() {
const response = await fetch('https://api.example.com/data');
// JSON
const jsonData = await response.json();
// Text
const textData = await response.text();
// Blob
const blobData = await response.blob();
// ArrayBuffer
const bufferData = await response.arrayBuffer();
// FormData
const formData = await response.formData();
}
Response Properties
async function checkResponse() {
const response = await fetch('https://api.example.com/data');
console.log(response.status); // 200
console.log(response.statusText); // "OK"
console.log(response.ok); // true
console.log(response.headers); // Headers object
console.log(response.url); // Request URL
console.log(response.type); // "cors", "basic", etc.
}
Error Handling
Basic Error Handling
async function fetchWithErrorHandling() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
Handling Different Error Types
async function robustFetch(url) {
try {
const response = await fetch(url);
// Check for HTTP errors
if (!response.ok) {
if (response.status === 404) {
throw new Error('Resource not found');
} else if (response.status === 500) {
throw new Error('Server error');
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
// Parse JSON
const data = await response.json();
return data;
} catch (error) {
// Handle network errors
if (error instanceof TypeError) {
console.error('Network error:', error.message);
} else {
console.error('Error:', error.message);
}
throw error;
}
}
Practical Examples
Example 1: Fetching User Data
async function getUser(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error('Error fetching user:', error);
throw error;
}
}
// Usage
getUser(1)
.then(user => console.log('User:', user))
.catch(error => console.error('Error:', error));
Example 2: Creating a Resource
async function createUser(userData) {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Failed to create user');
}
const newUser = await response.json();
return newUser;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
}
// Usage
createUser({ name: 'Alice', email: 'alice@example.com' })
.then(user => console.log('Created:', user))
.catch(error => console.error('Error:', error));
Example 3: Updating a Resource
async function updateUser(userId, updates) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});
if (!response.ok) {
throw new Error(`Failed to update user: ${response.status}`);
}
const updatedUser = await response.json();
return updatedUser;
} catch (error) {
console.error('Error updating user:', error);
throw error;
}
}
Example 4: Deleting a Resource
async function deleteUser(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`Failed to delete user: ${response.status}`);
}
// Some APIs return data, others return empty
if (response.status === 204) {
return { success: true };
}
return await response.json();
} catch (error) {
console.error('Error deleting user:', error);
throw error;
}
}
Example 5: Fetching Multiple Resources
async function fetchMultipleUsers(userIds) {
try {
const promises = userIds.map(id =>
fetch(`https://api.example.com/users/${id}`)
.then(response => {
if (!response.ok) {
throw new Error(`Failed to fetch user ${id}`);
}
return response.json();
})
);
const users = await Promise.all(promises);
return users;
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
}
// Usage
fetchMultipleUsers([1, 2, 3])
.then(users => console.log('Users:', users))
.catch(error => console.error('Error:', error));
Practice Exercise
Exercise: Fetch API Practice
Objective: Practice making HTTP requests with the Fetch API.
Instructions:
-
Create a file called
fetch-practice.js -
Practice:
- GET requests
- POST requests
- PUT requests
- DELETE requests
- Error handling
- Response handling
- Multiple requests
-
Use a mock API or JSONPlaceholder (https://jsonplaceholder.typicode.com)
Example Solution:
// Fetch API Practice
const API_URL = 'https://jsonplaceholder.typicode.com';
console.log("=== GET Request ===");
async function getUsers() {
try {
const response = await fetch(`${API_URL}/users`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json();
console.log('Users:', users);
return users;
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
}
getUsers().then(users => {
console.log(`Fetched ${users.length} users`);
});
console.log();
console.log("=== GET Single User ===");
async function getUser(userId) {
try {
const response = await fetch(`${API_URL}/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
console.log('User:', user);
return user;
} catch (error) {
console.error('Error fetching user:', error);
throw error;
}
}
getUser(1).then(user => {
console.log(`User name: ${user.name}`);
});
console.log();
console.log("=== POST Request ===");
async function createPost(postData) {
try {
const response = await fetch(`${API_URL}/posts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const newPost = await response.json();
console.log('Created post:', newPost);
return newPost;
} catch (error) {
console.error('Error creating post:', error);
throw error;
}
}
createPost({
title: 'My New Post',
body: 'This is the body of my post',
userId: 1
}).then(post => {
console.log(`Post created with ID: ${post.id}`);
});
console.log();
console.log("=== PUT Request ===");
async function updatePost(postId, updates) {
try {
const response = await fetch(`${API_URL}/posts/${postId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const updatedPost = await response.json();
console.log('Updated post:', updatedPost);
return updatedPost;
} catch (error) {
console.error('Error updating post:', error);
throw error;
}
}
updatePost(1, {
title: 'Updated Title',
body: 'Updated body',
userId: 1
}).then(post => {
console.log(`Post ${post.id} updated`);
});
console.log();
console.log("=== DELETE Request ===");
async function deletePost(postId) {
try {
const response = await fetch(`${API_URL}/posts/${postId}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log(`Post ${postId} deleted`);
return { success: true };
} catch (error) {
console.error('Error deleting post:', error);
throw error;
}
}
deletePost(1).then(result => {
console.log('Delete result:', result);
});
console.log();
console.log("=== Error Handling ===");
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url);
if (!response.ok) {
if (response.status === 404) {
throw new Error('Resource not found');
} else if (response.status >= 500) {
throw new Error('Server error');
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
const data = await response.json();
return data;
} catch (error) {
if (error instanceof TypeError) {
console.error('Network error:', error.message);
} else {
console.error('Error:', error.message);
}
throw error;
}
}
// Test with invalid URL
fetchWithErrorHandling(`${API_URL}/invalid`)
.catch(error => {
console.log('Caught error:', error.message);
});
console.log();
console.log("=== Multiple Requests ===");
async function fetchUserPosts(userId) {
try {
// Fetch user and posts in parallel
const [userResponse, postsResponse] = await Promise.all([
fetch(`${API_URL}/users/${userId}`),
fetch(`${API_URL}/users/${userId}/posts`)
]);
if (!userResponse.ok || !postsResponse.ok) {
throw new Error('Failed to fetch data');
}
const [user, posts] = await Promise.all([
userResponse.json(),
postsResponse.json()
]);
console.log(`User: ${user.name}`);
console.log(`Posts: ${posts.length}`);
return { user, posts };
} catch (error) {
console.error('Error:', error);
throw error;
}
}
fetchUserPosts(1).then(data => {
console.log('User data:', data);
});
console.log();
console.log("=== Response Headers ===");
async function checkHeaders() {
try {
const response = await fetch(`${API_URL}/users/1`);
console.log('Status:', response.status);
console.log('Status Text:', response.statusText);
console.log('OK:', response.ok);
console.log('Content-Type:', response.headers.get('Content-Type'));
console.log('URL:', response.url);
} catch (error) {
console.error('Error:', error);
}
}
checkHeaders();
Expected Output (will vary based on API response):
=== GET Request ===
Users: [array of users]
Fetched 10 users
=== GET Single User ===
User: { id: 1, name: "Leanne Graham", ... }
User name: Leanne Graham
=== POST Request ===
Created post: { id: 101, title: "My New Post", ... }
Post created with ID: 101
=== PUT Request ===
Updated post: { id: 1, title: "Updated Title", ... }
Post 1 updated
=== DELETE Request ===
Post 1 deleted
Delete result: { success: true }
=== Error Handling ===
Error: Resource not found
Caught error: Resource not found
=== Multiple Requests ===
User: Leanne Graham
Posts: 10
User data: { user: {...}, posts: [...] }
=== Response Headers ===
Status: 200
Status Text: OK
OK: true
Content-Type: application/json; charset=utf-8
URL: https://jsonplaceholder.typicode.com/users/1
Challenge (Optional):
- Build a complete CRUD application with Fetch
- Create a reusable API client
- Implement retry logic
- Add request timeout handling
Common Mistakes
1. Not Checking response.ok
// ⚠️ Problem: Doesn't check for HTTP errors
const response = await fetch(url);
const data = await response.json(); // May fail if response is error
// ✅ Solution: Always check response.ok
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
2. Forgetting to Parse JSON
// ⚠️ Problem: Response is not parsed
const response = await fetch(url);
console.log(response); // Response object, not data
// ✅ Solution: Parse response
const response = await fetch(url);
const data = await response.json();
console.log(data); // Actual data
3. Not Handling Errors
// ⚠️ Problem: No error handling
const data = await fetch(url).then(r => r.json());
// ✅ Solution: Always handle errors
try {
const response = await fetch(url);
if (!response.ok) throw new Error('HTTP error');
const data = await response.json();
} catch (error) {
console.error('Error:', error);
}
4. Missing Headers for POST/PUT
// ⚠️ Problem: Missing Content-Type header
fetch(url, {
method: 'POST',
body: JSON.stringify(data) // Server may not parse correctly
});
// ✅ Solution: Include Content-Type
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
Key Takeaways
- Fetch API: Modern, promise-based HTTP client
- HTTP Methods: GET, POST, PUT, DELETE
- Request Options: Headers, body, method, etc.
- Response Handling: Check status, parse data
- Error Handling: Always check response.ok, handle errors
- async/await: Preferred over .then() chains
- Best Practice: Always handle errors, check response status
- Modern Standard: Fetch is the standard for HTTP requests
Quiz: Fetch API
Test your understanding with these questions:
-
Fetch returns:
- A) Data directly
- B) Promise
- C) Response object
- D) Error
-
To parse JSON response:
- A) response.json()
- B) response.text()
- C) JSON.parse(response)
- D) response.data
-
POST request needs:
- A) method: 'POST'
- B) body
- C) headers (usually)
- D) All of the above
-
response.ok is:
- A) Always true
- B) True for 2xx status
- C) True for 200 only
- D) Always false
-
Fetch is:
- A) Synchronous
- B) Asynchronous
- C) Both
- D) Neither
-
Error handling should:
- A) Check response.ok
- B) Use try-catch
- C) Both A and B
- D) Neither
-
Multiple requests can use:
- A) Promise.all()
- B) Sequential await
- C) Both
- D) Neither
Answers:
- B) Promise (that resolves to Response)
- A) response.json()
- D) All of the above
- B) True for 2xx status codes
- B) Asynchronous
- C) Both A and B
- C) Both (Promise.all for parallel, sequential await for dependent)
Next Steps
Congratulations! You've learned the Fetch API. You now know:
- How to make HTTP requests
- All HTTP methods (GET, POST, PUT, DELETE)
- How to handle responses and errors
- Best practices for API calls
What's Next?
- Lesson 12.2: Axios and HTTP Libraries
- Learn about alternative HTTP libraries
- Understand when to use different tools
- Build more complex API integrations
Additional Resources
- MDN: Fetch API: developer.mozilla.org/en-US/docs/Web/API/Fetch_API
- Fetch API Guide: Comprehensive examples and patterns
- JSONPlaceholder: jsonplaceholder.typicode.com - Free fake API for testing
Lesson completed! You're ready to move on to the next lesson.