Table of Contents
- Understanding Fetch API Basics
- Making GET Requests
- Handling Different Response Types
- Making POST Requests
- Working with Other HTTP Methods
- Error Handling in Fetch
- Advanced Concepts
- Fetch vs. XMLHttpRequest
- Best Practices
- Conclusion
- References
1. Understanding Fetch API Basics
At its core, the Fetch API is a global method (fetch()) that initiates an HTTP request to a specified URL and returns a promise. This promise resolves to a Response object, which contains metadata about the response (status code, headers) and the response body.
Syntax
The basic syntax of fetch() is:
fetch(url [, options])
.then(response => { /* handle response */ })
.catch(error => { /* handle network errors */ });
url: The endpoint to request (e.g.,https://api.example.com/data).options(optional): A configuration object to customize the request (method, headers, body, etc.).
The options Object
The options parameter lets you configure the request in detail. Common properties include:
| Property | Description |
|---|---|
method | HTTP method (GET, POST, PUT, DELETE, etc.). Default: GET. |
headers | Request headers (e.g., Content-Type, Authorization). |
body | Data to send in the request body (for POST, PUT, etc.). Must be a string, FormData, Blob, etc. |
mode | Controls CORS behavior (cors, no-cors, same-origin). Default: cors. |
credentials | Whether to include cookies in the request (omit, same-origin, include). Default: omit. |
signal | An AbortSignal object to abort the request (via AbortController). |
2. Making GET Requests: Fetching Data
The most common use case for Fetch is fetching data from a server using the GET method (the default). Let’s walk through a simple example using the JSONPlaceholder API (a free fake REST API for testing).
Example: Fetching a List of Posts
// Fetch posts from JSONPlaceholder
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => {
// Check if the request was successful (status 200-299)
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
// Parse the response body as JSON
return response.json();
})
.then(posts => {
console.log('Fetched posts:', posts); // Array of post objects
})
.catch(error => {
console.error('Fetch error:', error); // Handles network errors or HTTP errors
});
Key Notes:
- The
response.json()method parses the response body as JSON and returns a new promise (hence the second.then()). response.okis a boolean that checks if the HTTP status code is between 200-299 (success).- Fetch does not reject on HTTP errors (e.g., 404, 500). You must explicitly check
response.okto handle these cases (more on this in Error Handling).
3. Handling Different Response Types
The Response object provides methods to parse the response body into various formats. Here are the most common:
| Method | Description |
|---|---|
response.json() | Parses the body as JSON (returns Promise<JSON>). |
response.text() | Parses the body as plain text (returns Promise<String>). |
response.blob() | Parses the body as a binary blob (e.g., images, files; returns Promise<Blob>). |
response.formData() | Parses the body as FormData (for form submissions; returns Promise<FormData>). |
response.arrayBuffer() | Parses the body as an ArrayBuffer (binary data; returns Promise<ArrayBuffer>). |
Example: Fetching Text (HTML/CSV)
// Fetch a plain text file
fetch('https://example.com/terms.txt')
.then(response => response.text())
.then(text => console.log('Text content:', text));
Example: Fetching an Image (Blob)
// Fetch an image and display it in the DOM
fetch('https://example.com/image.jpg')
.then(response => response.blob())
.then(blob => {
const img = document.createElement('img');
img.src = URL.createObjectURL(blob); // Convert blob to a URL
document.body.appendChild(img);
});
4. Making POST Requests: Sending Data
To send data to a server (e.g., form submissions, user input), use the POST method. You’ll need to configure the options object with method: 'POST', headers, and a body.
Example 1: Sending JSON Data
When sending JSON, set the Content-Type header to application/json and stringify the data with JSON.stringify().
const newPost = {
title: 'My First Post',
body: 'Hello, Fetch API!',
userId: 1
};
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST', // Specify the method
headers: {
'Content-Type': 'application/json', // Indicate JSON data
// Optional: Add authentication tokens (e.g., 'Authorization': 'Bearer <token>')
},
body: JSON.stringify(newPost) // Convert data to JSON string
})
.then(response => response.json())
.then(data => {
console.log('Created post:', data); // { id: 101, title: 'My First Post', ... }
})
.catch(error => console.error('POST error:', error));
Example 2: Sending Form Data
For form submissions, use the FormData API to package key-value pairs. Fetch automatically sets the Content-Type to multipart/form-data when sending FormData, so you don’t need to manually define it.
// Create a FormData object
const formData = new FormData();
formData.append('username', 'john_doe');
formData.append('email', '[email protected]');
formData.append('avatar', avatarFile); // Attach a file (e.g., from an <input type="file">)
fetch('https://example.com/upload', {
method: 'POST',
body: formData // No need for Content-Type header; Fetch handles it
})
.then(response => response.json())
.then(data => console.log('Upload response:', data));
5. Working with Other HTTP Methods (PUT, DELETE, PATCH)
Fetch supports all HTTP methods, including PUT (update), DELETE (remove), and PATCH (partial update). The syntax is similar to POST—simply set the method in the options object.
Example: Updating Data with PUT
To update an existing resource (e.g., edit a post), use PUT:
const updatedPost = {
title: 'Updated Post Title',
body: 'This post was updated via Fetch!',
userId: 1
};
fetch('https://jsonplaceholder.typicode.com/posts/1', { // Update post with ID 1
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedPost)
})
.then(response => response.json())
.then(data => console.log('Updated post:', data));
Example: Deleting Data with DELETE
To delete a resource, use DELETE. Often, no body is needed, but some APIs may require it.
fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
console.log('Post deleted successfully!');
}
})
.catch(error => console.error('Delete error:', error));
6. Error Handling in Fetch
One of the most common pitfalls with Fetch is misunderstanding how errors are handled. Unlike XMLHttpRequest, Fetch does not reject promises on HTTP errors (e.g., 404, 500). Instead, it resolves with a Response object, and you must explicitly check for errors using response.ok.
Proper Error Handling Workflow:
- Check
response.okto catch HTTP errors (4xx, 5xx). - Throw an error if
!response.okto trigger the.catch()block. - Use
.catch()to handle network errors (e.g., no internet, invalid URL).
Example with Async/Await (Cleaner Syntax)
Using async/await (introduced in ES2017) makes error handling more readable than chaining .then() and .catch().
async function fetchPost(postId) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`);
// Check for HTTP errors
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const post = await response.json();
console.log('Fetched post:', post);
return post;
} catch (error) {
// Handles network errors OR HTTP errors (thrown above)
console.error('Error fetching post:', error.message);
throw error; // Re-throw to let the caller handle it
}
}
// Usage
fetchPost(999); // 404 error (post 999 doesn't exist)
7. Advanced Concepts
7.1 Custom Headers
To send custom headers (e.g., authentication tokens, API keys), use the headers option. You can pass a plain object or a Headers object for more control.
// Using a plain object
fetch('https://api.example.com/data', {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'X-Custom-Header': 'Hello'
}
});
// Using the Headers API (more flexible)
const headers = new Headers();
headers.append('Authorization', 'Bearer YOUR_API_TOKEN');
headers.append('Accept', 'application/json');
fetch('https://api.example.com/data', { headers });
Note: Some headers (e.g., Content-Type, Authorization) are “safe” to set, but others (e.g., Origin, Host) are controlled by the browser and cannot be modified.
7.2 CORS (Cross-Origin Resource Sharing)
CORS is a security feature enforced by browsers that restricts HTTP requests from one origin (domain) to another. If your Fetch request targets a different origin (e.g., http://localhost:3000 calling https://api.example.com), the server must include CORS headers (e.g., Access-Control-Allow-Origin) in its response.
Common CORS Issues:
- No
Access-Control-Allow-Originheader: The browser blocks the response. Fix: Configure the server to includeAccess-Control-Allow-Origin: *(for development) or your specific origin. - Preflight Requests: For non-simple requests (e.g.,
PUT,DELETE, custom headers), the browser sends a preflightOPTIONSrequest to check if the server allows the actual request. The server must respond withAccess-Control-Allow-MethodsandAccess-Control-Allow-Headersto proceed.
Tip: Use browser dev tools (Network tab) to inspect preflight requests and debug CORS errors.
7.3 Aborting Requests with AbortController
Sometimes you need to cancel a Fetch request (e.g., if the user navigates away from a page or a timeout occurs). The AbortController API lets you do this by linking a signal to the request.
Example: Abort a Request After 5 Seconds
// Create an AbortController
const controller = new AbortController();
const signal = controller.signal;
// Set a timeout to abort the request after 5 seconds
const timeoutId = setTimeout(() => {
controller.abort(); // Abort the request
console.log('Request aborted due to timeout');
}, 5000);
// Link the signal to the Fetch request
fetch('https://jsonplaceholder.typicode.com/posts', { signal })
.then(response => response.json())
.then(posts => {
clearTimeout(timeoutId); // Cancel the timeout if request succeeds
console.log('Posts:', posts);
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request aborted intentionally');
} else {
console.error('Fetch error:', error);
}
});
8. Fetch vs. XMLHttpRequest
Before Fetch, XMLHttpRequest (XHR) was the primary tool for async HTTP requests. Here’s how they compare:
| Feature | Fetch API | XMLHttpRequest |
|---|---|---|
| Syntax | Promise-based, clean and readable. | Callback-based, verbose. |
| Error Handling | Requires manual check of response.ok. | Triggers onerror for network errors; status code must be checked manually. |
| Abort Support | Built-in via AbortController. | Requires abort() method (less flexible). |
| Response Parsing | Built-in methods (json(), text(), etc.). | Manual parsing (e.g., JSON.parse(xhr.responseText)). |
| Browser Support | Supported in all modern browsers (IE11+ with polyfills). | Supported in all browsers (legacy). |
Verdict: Fetch is preferred for modern applications due to its promise-based design, cleaner syntax, and integration with modern JS features (async/await, AbortController). XHR is only needed for legacy browser support.
9. Best Practices
To use Fetch effectively, follow these best practices:
- Use Async/Await for Readability: Async/await makes code flatter and easier to debug than
.then()chains. - Always Handle Errors: Explicitly check
response.okand usetry/catchto handle both network and HTTP errors. - Sanitize Input: Validate and sanitize data before sending it in requests to prevent injection attacks.
- Set Appropriate Headers: Include
Content-Type(e.g.,application/json) and authentication headers (e.g.,Authorization) when needed. - Abort Stale Requests: Use
AbortControllerto cancel requests that are no longer needed (e.g., user navigates away). - Cache Responses: For static data, cache responses (e.g., with
localStorageor the Cache API) to reduce redundant network calls. - Respect CORS: Work with backend teams to configure CORS headers correctly for cross-origin requests.
10. Conclusion
The Fetch API has revolutionized how JavaScript handles HTTP requests, offering a modern, promise-based alternative to XMLHttpRequest. Its flexibility, support for all HTTP methods, and integration with async/await make it indispensable for building dynamic web applications.
By mastering Fetch, you can seamlessly fetch, send, and manipulate data from APIs, handle errors gracefully, and build responsive user experiences. Remember to follow best practices like error handling, CORS awareness, and request abortion to ensure robust and efficient code.