65% complete
JavaScript Fetch API
The Fetch API is a modern interface for making HTTP requests in JavaScript. It provides a more powerful and flexible feature set than older techniques like XMLHttpRequest, with a cleaner, promise-based API that's easier to use.
What You'll Learn
- How to make basic GET and POST requests
- Working with JSON data
- Handling response status codes
- Error handling with fetch
- Setting request headers
- Using request options (credentials, CORS, etc.)
- Advanced techniques like request cancellation and timeouts
Browser Compatibility
The Fetch API is supported in all modern browsers, but not in Internet Explorer. For broader compatibility, consider using a polyfill or libraries like Axios.
Basic Fetch Request
At its simplest, the Fetch API takes a URL and returns a Promise that resolves to a Response object:
1// Basic GET request2fetch('https://api.example.com/data')3 .then(response => {4 // Response handling5 return response.json(); // Parse JSON response6 })7 .then(data => {8 // Work with the data9 console.log(data);10 })11 .catch(error => {12 // Error handling13 console.error('Error:', error);14 });
With async/await, this becomes even cleaner:
1// Using async/await2async function fetchData() {3 try {4 const response = await fetch('https://api.example.com/data');5 const data = await response.json();6 console.log(data);7 } catch (error) {8 console.error('Error:', error);9 }10}1112fetchData();
Understanding the Response Object
The Response object contains information about the HTTP response, including status, headers, and body.
1async function examineResponse() {2 const response = await fetch('https://api.example.com/data');34 // Status information5 console.log('Status:', response.status); // e.g., 2006 console.log('Status Text:', response.statusText); // e.g., "OK"7 console.log('OK?', response.ok); // true if status is 200-29989 // Response type and URL10 console.log('Type:', response.type); // "basic", "cors", etc.11 console.log('URL:', response.url);1213 // Headers14 console.log('Content-Type:', response.headers.get('content-type'));1516 // Body methods (can only be used once)17 const data = await response.json(); // Parse as JSON18 // OR other formats:19 // const text = await response.text();20 // const blob = await response.blob();21 // const formData = await response.formData();22 // const arrayBuffer = await response.arrayBuffer();2324 return data;25}
Important Response Methods
Method | Description | Use Case |
---|---|---|
response.json() | Parses response as JSON | API responses with JSON data |
response.text() | Returns response as text | HTML, XML, plain text responses |
response.blob() | Returns response as Blob | Images, files, binary data |
response.formData() | Parses response as FormData | Form submissions |
response.arrayBuffer() | Returns response as ArrayBuffer | Binary data processing |
Note: These body methods can only be used once per response. The response body can only be consumed once.
HTTP Request Methods with Fetch
The Fetch API supports all HTTP methods (GET, POST, PUT, DELETE, etc.) through the method option:
GET Request (Default)
1// GET request (default method)2fetch('https://api.example.com/users')3 .then(response => response.json())4 .then(data => console.log(data));56// GET with query parameters7fetch('https://api.example.com/users?role=admin&status=active')8 .then(response => response.json())9 .then(data => console.log(data));1011// Alternatively, you can use a URL object12const url = new URL('https://api.example.com/users');13url.searchParams.append('role', 'admin');14url.searchParams.append('status', 'active');1516fetch(url)17 .then(response => response.json())18 .then(data => console.log(data));
POST Request
1// POST request with JSON data2fetch('https://api.example.com/users', {3 method: 'POST',4 headers: {5 'Content-Type': 'application/json',6 },7 body: JSON.stringify({8 name: 'John Doe',9 email: 'john@example.com',10 age: 3011 })12})13 .then(response => response.json())14 .then(data => console.log(data))15 .catch(error => console.error('Error:', error));1617// POST with form data18const formData = new FormData();19formData.append('name', 'John Doe');20formData.append('email', 'john@example.com');21formData.append('profilePicture', fileInput.files[0]);2223fetch('https://api.example.com/users', {24 method: 'POST',25 body: formData26})27 .then(response => response.json())28 .then(data => console.log(data))29 .catch(error => console.error('Error:', error));
PUT and DELETE Requests
1// PUT request to update a resource2fetch('https://api.example.com/users/123', {3 method: 'PUT',4 headers: {5 'Content-Type': 'application/json',6 },7 body: JSON.stringify({8 name: 'John Smith', // Updated name9 email: 'john@example.com',10 age: 31 // Updated age11 })12})13 .then(response => response.json())14 .then(data => console.log(data));1516// DELETE request17fetch('https://api.example.com/users/123', {18 method: 'DELETE'19})20 .then(response => {21 if (response.ok) {22 console.log('User deleted successfully');23 } else {24 console.error('Failed to delete user');25 }26 });
Request Options
The fetch() function accepts a second parameter, an options object that lets you control various aspects of the HTTP request:
1fetch('https://api.example.com/data', {2 // HTTP Method3 method: 'POST',45 // Headers6 headers: {7 'Content-Type': 'application/json',8 'Authorization': 'Bearer YOUR_TOKEN_HERE',9 'Accept': 'application/json'10 },1112 // Request body13 body: JSON.stringify({ key: 'value' }),1415 // Credentials: include cookies16 credentials: 'include', // 'omit', 'same-origin', or 'include'1718 // Cache control19 cache: 'no-cache', // 'default', 'no-store', 'reload', 'no-cache', 'force-cache', or 'only-if-cached'2021 // Redirect behavior22 redirect: 'follow', // 'follow', 'error', or 'manual'2324 // Referrer policy25 referrerPolicy: 'no-referrer', // Various options available2627 // Mode (affects CORS)28 mode: 'cors', // 'cors', 'no-cors', 'same-origin', or 'navigate'29})
Authentication with Fetch
For authenticated requests, you typically add an Authorization header:
fetch('https://api.example.com/protected-resource', {headers: {'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'}}).then(response => response.json()).then(data => console.log(data));
Handling Errors Properly
One important thing to note about fetch() is that it only rejects the promise when a network error occurs. HTTP error responses (like 404 or 500) are still considered successful responses, so you need to check the status manually:
1// Complete error handling2async function fetchWithErrorHandling(url) {3 try {4 const response = await fetch(url);56 // Check if the request was successful7 if (!response.ok) {8 throw new Error(`HTTP error! Status: ${response.status}`);9 }1011 const data = await response.json();12 return data;13 } catch (error) {14 if (error instanceof TypeError) {15 // Network error (e.g., no internet connection)16 console.error('Network error:', error.message);17 } else {18 // HTTP error or JSON parsing error19 console.error('Error:', error.message);20 }21 throw error; // Re-throw to allow further handling22 }23}2425// Usage26fetchWithErrorHandling('https://api.example.com/data')27 .then(data => console.log('Success:', data))28 .catch(error => console.error('Handler caught:', error.message));
Advanced Fetch Techniques
Request Cancellation with AbortController
The AbortController API allows you to cancel fetch requests that are in progress:
1// Create an AbortController2const controller = new AbortController();3const signal = controller.signal;45// Start the fetch6fetch('https://api.example.com/large-data', { signal })7 .then(response => response.json())8 .then(data => console.log('Data received:', data))9 .catch(error => {10 if (error.name === 'AbortError') {11 console.log('Fetch was aborted');12 } else {13 console.error('Error:', error);14 }15 });1617// Cancel the fetch after 5 seconds18setTimeout(() => {19 controller.abort();20 console.log('Fetch aborted due to timeout');21}, 5000);
Implementing a Timeout
Since fetch() doesn't have a built-in timeout option, you can implement one using AbortController:
1function fetchWithTimeout(url, options = {}, timeout = 5000) {2 const controller = new AbortController();3 const { signal } = controller;45 // Set up the timeout6 const timeoutId = setTimeout(() => controller.abort(), timeout);78 // Start the fetch with the abort signal9 return fetch(url, { ...options, signal })10 .then(response => {11 clearTimeout(timeoutId); // Clear the timeout if the fetch completes12 return response;13 })14 .catch(error => {15 if (error.name === 'AbortError') {16 throw new Error(`Request timed out after ${timeout}ms`);17 }18 throw error;19 });20}2122// Usage23fetchWithTimeout('https://api.example.com/data', {}, 3000)24 .then(response => response.json())25 .then(data => console.log(data))26 .catch(error => console.error(error.message));
Working with Files and Images
The Fetch API makes it easy to work with binary data like images and files:
1// Uploading a file2const fileInput = document.querySelector('#fileInput');3const file = fileInput.files[0];45const formData = new FormData();6formData.append('file', file);78fetch('https://api.example.com/upload', {9 method: 'POST',10 body: formData11})12 .then(response => response.json())13 .then(data => console.log('File uploaded successfully:', data));1415// Downloading and displaying an image16fetch('https://example.com/image.jpg')17 .then(response => response.blob())18 .then(blob => {19 const imgUrl = URL.createObjectURL(blob);20 const imgElement = document.createElement('img');21 imgElement.src = imgUrl;22 document.body.appendChild(imgElement);23 });
CORS: Cross-Origin Resource Sharing
CORS is a security feature implemented by browsers that restricts cross-origin HTTP requests. When fetching resources from a different origin (domain, protocol, or port), the server needs to include appropriate CORS headers.
Common CORS Errors
If you see an error like: "Access to fetch at 'https://api.example.com' from origin 'https://your-site.com' has been blocked by CORS policy," it means the server doesn't allow cross-origin requests from your domain.
Solutions:
- Configure the server to send proper CORS headers
- Use a proxy server in development
- Set
mode: 'no-cors'
(but this limits what you can do with the response)
Practical Examples
Complete Login Form Example
1// HTML2// <form id="loginForm">3// <input type="email" id="email" required>4// <input type="password" id="password" required>5// <button type="submit">Login</button>6// <p id="message"></p>7// </form>89// JavaScript10document.getElementById('loginForm').addEventListener('submit', async (e) => {11 e.preventDefault();1213 const messageElement = document.getElementById('message');14 messageElement.textContent = 'Logging in...';1516 const email = document.getElementById('email').value;17 const password = document.getElementById('password').value;1819 try {20 const response = await fetch('https://api.example.com/login', {21 method: 'POST',22 headers: {23 'Content-Type': 'application/json'24 },25 body: JSON.stringify({ email, password }),26 credentials: 'include' // Include cookies27 });2829 if (!response.ok) {30 const errorData = await response.json();31 throw new Error(errorData.message || 'Login failed');32 }3334 const data = await response.json();3536 // Store token in localStorage37 localStorage.setItem('token', data.token);3839 messageElement.textContent = 'Login successful!';40 messageElement.style.color = 'green';4142 // Redirect to dashboard43 window.location.href = '/dashboard';44 } catch (error) {45 messageElement.textContent = `Error: ${error.message}`;46 messageElement.style.color = 'red';47 }48});
Fetch Data and Render List
1// HTML2// <div id="userList"></div>3// <button id="loadUsers">Load Users</button>45// JavaScript6document.getElementById('loadUsers').addEventListener('click', fetchUsers);78async function fetchUsers() {9 const userListElement = document.getElementById('userList');10 userListElement.innerHTML = '<p>Loading users...</p>';1112 try {13 const response = await fetch('https://jsonplaceholder.typicode.com/users');1415 if (!response.ok) {16 throw new Error(`HTTP error! Status: ${response.status}`);17 }1819 const users = await response.json();2021 // Clear loading message22 userListElement.innerHTML = '';2324 // Create the list25 const ul = document.createElement('ul');2627 users.forEach(user => {28 const li = document.createElement('li');29 li.innerHTML = `30 <strong>${user.name}</strong> (@${user.username})<br>31 <a href="mailto:${user.email}">${user.email}</a><br>32 <small>${user.company.name}</small>33 `;34 ul.appendChild(li);35 });3637 userListElement.appendChild(ul);38 } catch (error) {39 userListElement.innerHTML = `<p style="color: red">Error: ${error.message}</p>`;40 }41}
Fetch vs. Alternatives
Method | Pros | Cons |
---|---|---|
Fetch API |
|
|
XMLHttpRequest |
|
|
Axios |
|
|
Best Practices and Tips
- Always check response.ok to handle HTTP error status codes properly
- Use try/catch with async/await for cleaner error handling
- Implement timeouts for requests that might take too long
- Consider creating a wrapper for fetch that handles common patterns in your app
- Set appropriate Content-Type headers when sending data
- Use AbortController to cancel requests when components unmount
- Consider request states (loading, success, error) in your UI
Creating a Reusable Fetch Utility
For larger applications, it's often helpful to create a utility function that encapsulates common fetch patterns:
1// api.js - A simple fetch wrapper2const API_BASE_URL = 'https://api.example.com';34export async function api(endpoint, { method = 'GET', body = null, headers = {} } = {}) {5 const token = localStorage.getItem('token');67 const config = {8 method,9 headers: {10 'Content-Type': 'application/json',11 'Authorization': token ? `Bearer ${token}` : '',12 ...headers13 },14 credentials: 'include'15 };1617 if (body) {18 config.body = JSON.stringify(body);19 }2021 try {22 const response = await fetch(`${API_BASE_URL}${endpoint}`, config);2324 if (!response.ok) {25 const errorData = await response.json().catch(() => ({}));26 throw new Error(errorData.message || `Error ${response.status}: ${response.statusText}`);27 }2829 // Check if there is a response body30 const contentType = response.headers.get('content-type');31 if (contentType && contentType.includes('application/json')) {32 return await response.json();33 }3435 return response;36 } catch (error) {37 console.error('API request failed:', error);38 throw error;39 }40}4142// Usage43// api('/users') // GET request44// api('/users', { method: 'POST', body: { name: 'John' } }) // POST request
Summary
The Fetch API is a powerful, modern way to make HTTP requests in JavaScript. It provides a clean, promise-based interface that works well with async/await, making asynchronous code easier to read and maintain. While it has a few quirks (like its error handling behavior), it has become the standard method for making HTTP requests in modern web applications.
As you continue to build web applications, you'll find the Fetch API to be an essential tool for communicating with servers, consuming APIs, and creating dynamic, data-driven interfaces.
Related Tutorials
Learn about promises, async/await, and handling asynchronous operations.
Learn moreLearn how to handle user interactions with event listeners.
Learn moreLearn how to modify HTML elements with JavaScript.
Learn more