Progress13 of 20 topics

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:

JavaScript
1// Basic GET request
2fetch('https://api.example.com/data')
3 .then(response => {
4 // Response handling
5 return response.json(); // Parse JSON response
6 })
7 .then(data => {
8 // Work with the data
9 console.log(data);
10 })
11 .catch(error => {
12 // Error handling
13 console.error('Error:', error);
14 });

With async/await, this becomes even cleaner:

JavaScript
1// Using async/await
2async 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}
11
12fetchData();

Understanding the Response Object

The Response object contains information about the HTTP response, including status, headers, and body.

JavaScript
1async function examineResponse() {
2 const response = await fetch('https://api.example.com/data');
3
4 // Status information
5 console.log('Status:', response.status); // e.g., 200
6 console.log('Status Text:', response.statusText); // e.g., "OK"
7 console.log('OK?', response.ok); // true if status is 200-299
8
9 // Response type and URL
10 console.log('Type:', response.type); // "basic", "cors", etc.
11 console.log('URL:', response.url);
12
13 // Headers
14 console.log('Content-Type:', response.headers.get('content-type'));
15
16 // Body methods (can only be used once)
17 const data = await response.json(); // Parse as JSON
18 // 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();
23
24 return data;
25}

Important Response Methods

MethodDescriptionUse Case
response.json()Parses response as JSONAPI responses with JSON data
response.text()Returns response as textHTML, XML, plain text responses
response.blob()Returns response as BlobImages, files, binary data
response.formData()Parses response as FormDataForm submissions
response.arrayBuffer()Returns response as ArrayBufferBinary 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)

JavaScript
1// GET request (default method)
2fetch('https://api.example.com/users')
3 .then(response => response.json())
4 .then(data => console.log(data));
5
6// GET with query parameters
7fetch('https://api.example.com/users?role=admin&status=active')
8 .then(response => response.json())
9 .then(data => console.log(data));
10
11// Alternatively, you can use a URL object
12const url = new URL('https://api.example.com/users');
13url.searchParams.append('role', 'admin');
14url.searchParams.append('status', 'active');
15
16fetch(url)
17 .then(response => response.json())
18 .then(data => console.log(data));

POST Request

JavaScript
1// POST request with JSON data
2fetch('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: 30
11 })
12})
13 .then(response => response.json())
14 .then(data => console.log(data))
15 .catch(error => console.error('Error:', error));
16
17// POST with form data
18const formData = new FormData();
19formData.append('name', 'John Doe');
20formData.append('email', 'john@example.com');
21formData.append('profilePicture', fileInput.files[0]);
22
23fetch('https://api.example.com/users', {
24 method: 'POST',
25 body: formData
26})
27 .then(response => response.json())
28 .then(data => console.log(data))
29 .catch(error => console.error('Error:', error));

PUT and DELETE Requests

JavaScript
1// PUT request to update a resource
2fetch('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 name
9 email: 'john@example.com',
10 age: 31 // Updated age
11 })
12})
13 .then(response => response.json())
14 .then(data => console.log(data));
15
16// DELETE request
17fetch('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:

JavaScript
1fetch('https://api.example.com/data', {
2 // HTTP Method
3 method: 'POST',
4
5 // Headers
6 headers: {
7 'Content-Type': 'application/json',
8 'Authorization': 'Bearer YOUR_TOKEN_HERE',
9 'Accept': 'application/json'
10 },
11
12 // Request body
13 body: JSON.stringify({ key: 'value' }),
14
15 // Credentials: include cookies
16 credentials: 'include', // 'omit', 'same-origin', or 'include'
17
18 // Cache control
19 cache: 'no-cache', // 'default', 'no-store', 'reload', 'no-cache', 'force-cache', or 'only-if-cached'
20
21 // Redirect behavior
22 redirect: 'follow', // 'follow', 'error', or 'manual'
23
24 // Referrer policy
25 referrerPolicy: 'no-referrer', // Various options available
26
27 // 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:

JavaScript
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:

JavaScript
1// Complete error handling
2async function fetchWithErrorHandling(url) {
3 try {
4 const response = await fetch(url);
5
6 // Check if the request was successful
7 if (!response.ok) {
8 throw new Error(`HTTP error! Status: ${response.status}`);
9 }
10
11 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 error
19 console.error('Error:', error.message);
20 }
21 throw error; // Re-throw to allow further handling
22 }
23}
24
25// Usage
26fetchWithErrorHandling('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:

JavaScript
1// Create an AbortController
2const controller = new AbortController();
3const signal = controller.signal;
4
5// Start the fetch
6fetch('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 });
16
17// Cancel the fetch after 5 seconds
18setTimeout(() => {
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:

JavaScript
1function fetchWithTimeout(url, options = {}, timeout = 5000) {
2 const controller = new AbortController();
3 const { signal } = controller;
4
5 // Set up the timeout
6 const timeoutId = setTimeout(() => controller.abort(), timeout);
7
8 // Start the fetch with the abort signal
9 return fetch(url, { ...options, signal })
10 .then(response => {
11 clearTimeout(timeoutId); // Clear the timeout if the fetch completes
12 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}
21
22// Usage
23fetchWithTimeout('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:

JavaScript
1// Uploading a file
2const fileInput = document.querySelector('#fileInput');
3const file = fileInput.files[0];
4
5const formData = new FormData();
6formData.append('file', file);
7
8fetch('https://api.example.com/upload', {
9 method: 'POST',
10 body: formData
11})
12 .then(response => response.json())
13 .then(data => console.log('File uploaded successfully:', data));
14
15// Downloading and displaying an image
16fetch('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

JavaScript
1// HTML
2// <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>
8
9// JavaScript
10document.getElementById('loginForm').addEventListener('submit', async (e) => {
11 e.preventDefault();
12
13 const messageElement = document.getElementById('message');
14 messageElement.textContent = 'Logging in...';
15
16 const email = document.getElementById('email').value;
17 const password = document.getElementById('password').value;
18
19 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 cookies
27 });
28
29 if (!response.ok) {
30 const errorData = await response.json();
31 throw new Error(errorData.message || 'Login failed');
32 }
33
34 const data = await response.json();
35
36 // Store token in localStorage
37 localStorage.setItem('token', data.token);
38
39 messageElement.textContent = 'Login successful!';
40 messageElement.style.color = 'green';
41
42 // Redirect to dashboard
43 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

JavaScript
1// HTML
2// <div id="userList"></div>
3// <button id="loadUsers">Load Users</button>
4
5// JavaScript
6document.getElementById('loadUsers').addEventListener('click', fetchUsers);
7
8async function fetchUsers() {
9 const userListElement = document.getElementById('userList');
10 userListElement.innerHTML = '<p>Loading users...</p>';
11
12 try {
13 const response = await fetch('https://jsonplaceholder.typicode.com/users');
14
15 if (!response.ok) {
16 throw new Error(`HTTP error! Status: ${response.status}`);
17 }
18
19 const users = await response.json();
20
21 // Clear loading message
22 userListElement.innerHTML = '';
23
24 // Create the list
25 const ul = document.createElement('ul');
26
27 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 });
36
37 userListElement.appendChild(ul);
38 } catch (error) {
39 userListElement.innerHTML = `<p style="color: red">Error: ${error.message}</p>`;
40 }
41}

Fetch vs. Alternatives

MethodProsCons
Fetch API
  • Built into modern browsers
  • Promise-based
  • Clean, modern syntax
  • No built-in timeout
  • Error handling requires extra steps
  • Not supported in older browsers
XMLHttpRequest
  • Wider browser support
  • Upload progress events
  • Can sync requests (not recommended)
  • More complex API
  • Callback-based
  • Older, less elegant syntax
Axios
  • Consistent cross-browser behavior
  • Built-in features (timeouts, interceptors)
  • Automatic JSON parsing
  • External dependency
  • Adds to bundle size
  • May be overkill for simple apps

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:

JavaScript
1// api.js - A simple fetch wrapper
2const API_BASE_URL = 'https://api.example.com';
3
4export async function api(endpoint, { method = 'GET', body = null, headers = {} } = {}) {
5 const token = localStorage.getItem('token');
6
7 const config = {
8 method,
9 headers: {
10 'Content-Type': 'application/json',
11 'Authorization': token ? `Bearer ${token}` : '',
12 ...headers
13 },
14 credentials: 'include'
15 };
16
17 if (body) {
18 config.body = JSON.stringify(body);
19 }
20
21 try {
22 const response = await fetch(`${API_BASE_URL}${endpoint}`, config);
23
24 if (!response.ok) {
25 const errorData = await response.json().catch(() => ({}));
26 throw new Error(errorData.message || `Error ${response.status}: ${response.statusText}`);
27 }
28
29 // Check if there is a response body
30 const contentType = response.headers.get('content-type');
31 if (contentType && contentType.includes('application/json')) {
32 return await response.json();
33 }
34
35 return response;
36 } catch (error) {
37 console.error('API request failed:', error);
38 throw error;
39 }
40}
41
42// Usage
43// api('/users') // GET request
44// 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 more

Learn how to handle user interactions with event listeners.

Learn more

Learn how to modify HTML elements with JavaScript.

Learn more