Introduction:
The Fetch API is a crucial tool for handling network requests and interacting with external resources in modern JavaScript development. It provides a consistent and powerful way to make HTTP requests, supporting a wide range of use cases across different JavaScript runtimes. In this comprehensive guide, we’ll explore how to use the Fetch API in Node.js, Deno, and Bun, complete with practical coding examples. By the end of this article, you’ll be well-equipped to harness the full potential of the Fetch API in these environments.
Understanding the Fetch API
Before we delve into specific implementations, it’s essential to grasp the fundamentals of the Fetch API. This API standardizes the process of making HTTP requests and handling responses in JavaScript. It works by creating a request object, configuring it with various options (such as the HTTP method and headers), sending the request, and processing the response.
Whether you’re working in Node.js, Deno, or Bun, the core principles of the Fetch API remain consistent. Let’s start by exploring its usage in each environment.
Using the Fetch API in the Browser
In the browser, the Fetch API is readily available, allowing you to fetch data from various sources, including remote servers, APIs, and resources within the same origin. Here’s a simple example of using the Fetch API in a browser environment:
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
This example demonstrates the core steps of making a Fetch request in the browser:
- We use the
fetch
function, passing the URL of the resource we want to fetch as an argument. - The
fetch
function returns a Promise that resolves to aResponse
object representing the response to the request. - We call
.json()
on the response to parse the JSON data. - Finally, we handle the data or any errors that may occur.
Using the Fetch API in Node.js
Node.js does not have the Fetch API built-in like browsers do. However, you can easily add this functionality using the node-fetch
library. To use the Fetch API in Node.js, you need to install node-fetch
:
npm install node-fetch
Now, let’s see an example of using the Fetch API in a Node.js environment:
const fetch = require('node-fetch');
fetch(‘https://jsonplaceholder.typicode.com/posts/1’)
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error(‘Error:’, error);
});
In this Node.js example, we import the node-fetch
library and use it in a manner similar to the browser. The core concepts of creating a request and handling responses are consistent.
Using the Fetch API in Deno
Deno is a secure JavaScript and TypeScript runtime that offers built-in support for the Fetch API. No additional libraries or dependencies are required to use the Fetch API in Deno. Here’s how you can use it:
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
if (response.ok) {
const data = await response.json();
console.log(data);
} else {
console.error('Error:', response.status, response.statusText);
}
In Deno, you can use the await
keyword with the fetch
function, simplifying the code and making it more readable. Additionally, you have direct access to the Response
object and its properties.
Making GET and POST Requests
The Fetch API supports various HTTP methods, including GET and POST. Let’s look at examples for both request types.
GET Request
// GET request in the browser
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
POST Request
// POST request in the browser
const postBody = {
userId: 1,
id: 101,
title: 'Lorem ipsum',
body: 'Dolor sit amet',
};
fetch(‘https://jsonplaceholder.typicode.com/posts’, {method: ‘POST’,
body: JSON.stringify(postBody),
headers: {
‘Content-Type’: ‘application/json’,
},
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error(‘Error:’, error);
});
In these examples, we use the method
option to specify the HTTP method and the body
option to provide data for the POST request.
Handling Headers and Authentication
The Fetch API enables you to work with request and response headers, as well as manage authentication by including credentials in your requests.
Setting Request Headers
// Adding custom headers to a GET request
fetch('https://api.example.com/data', {
headers: {
'Authorization': 'Bearer YourAccessToken',
'Custom-Header': 'CustomValue',
},
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
Accessing Response Headers
// Accessing response headers
fetch('https://api.example.com/data')
.then(response => {
const customHeader = response.headers.get('Custom-Header');
console.log(`Custom Header: ${customHeader}`);
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
Authentication and Credentials
To handle authentication, you can include credentials like cookies, HTTP authentication, or client certificates in your requests:
// Including credentials in a request
fetch('https://api.example.com/secure-data', {
credentials: 'include', // 'same-origin', 'include', or 'omit'
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
The credentials
option can be set to ‘same-origin’, ‘include’, or ‘omit,’ depending on your requirements. ‘same-origin’ restricts credentials to same-origin requests, ‘include’ includes credentials with cross-origin requests, and ‘omit’ omits credentials.
Handling Errors and Status Codes
Properly handling errors and status codes is a critical aspect of working with the Fetch API. You should check the response’s status code to determine whether the request was successful or if an error occurred:
fetch('https://api.example.com/data')
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error(`Request failed with status ${response.status}`);
}
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
In this example, we use the response.ok
property to check if the status code falls within the 200-299 range. If not, we throw an error that provides information about the failed request.
Dealing with CORS (Cross-Origin Resource Sharing)
When making requests from a web page hosted on one domain to a server on another domain, you may encounter Cross-Origin Resource Sharing (CORS) restrictions. CORS is a security feature that prevents malicious websites from making unauthorized requests to other domains. To work with APIs on different domains, you’ll need to address CORS limitations.
You can configure the server to allow cross-origin requests and set the mode
option in your Fetch request to handle CORS:
// Making a cross-origin request with credentials
fetch('https://api.example.com/data', {
mode: 'cors',
credentials: 'include',
})
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error(`Request failed with status ${response.status}`);
}
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
In this example, we set the mode
option to ‘cors’ to explicitly indicate that the request is a cross-origin request. We also include credentials to send cookies or other authentication data with the request.
Making Fetch Requests Synchronous
By default, Fetch API requests are asynchronous, meaning they don’t block the execution of your code. However, there may be scenarios where you need to make synchronous requests. In the browser, you can achieve this using async/await
:
async function fetchSynchronously() {
try {
const response = await fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {const data = await response.json();
console.log(data);
} else {
throw new Error(`Request failed with status ${response.status}`);
}
} catch (error) {
console.error(‘Error:’, error);
}
}
fetchSynchronously();
In Node.js, you can use the node-fetch
library to make synchronous requests as well:
const fetch = require('node-fetch');
async function fetchSynchronously() {
try {
const response = await fetch(‘https://api.example.com/data’, {
method: ‘GET’,
headers: {
‘Content-Type’: ‘application/json’,
},
});
if (response.ok) {
const data = await response.json();
console.log(data);
} else {
throw new Error(`Request failed with status ${response.status}`);
}
} catch (error) {
console.error(‘Error:’, error);
}
}
fetchSynchronously();
It’s important to note that making synchronous requests is generally discouraged in web development, as it can block the UI and degrade the user experience. Asynchronous requests are the preferred way to ensure smooth application performance.
Advanced Fetch Usage: Streaming and Progress
The Fetch API offers advanced features, including streaming and progress tracking, which can be useful for tasks like downloading large files or monitoring upload progress.
Streaming Response
// Streaming response in the browser
fetch('https://api.example.com/large-file', {
method: 'GET',
headers: {
'Content-Type': 'application/octet-stream',
},
})
.then(response => {
const reader = response.body.getReader();
return new ReadableStream({start(controller) {
function read() {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
controller.enqueue(value);
read();
});
}
read();},
});
})
.then(stream => new Response(stream))
.then(response => response.blob())
.then(blob => {
console.log(‘Downloaded file:’, blob);
})
.catch error => {
console.error(‘Error:’, error);
});
In this example, we stream a large file’s response without reading the entire content into memory.
Tracking Request Progress
// Tracking request progress in the browser
fetch('https://api.example.com/upload', {
method: 'POST',
body: new FormData(document.querySelector('form')),
})
.then(response => {
if (response.body) {
const totalSize = +response.headers.get('Content-Length');
let loaded = 0;
const reader = response.body.getReader();
return new ReadableStream({start(controller) {
function read() {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
loaded += value.length;
console.log(`Progress: ${Math.round((loaded / totalSize) * 100)}%`);
controller.enqueue(value);
read();
});
}
read();
},
});
}
})
.then(stream => new Response(stream))
.then(response => response.text())
.then(data => {
console.log(‘Upload completed:’, data);
})
.catch(error => {
console.error(‘Error:’, error);
});
This example demonstrates how to track the progress of an upload request in the browser.
Conclusion
The Fetch API is a versatile and powerful tool for making HTTP requests in various JavaScript runtimes, including the browser, Node.js, and Deno. Whether you are fetching data from remote servers, posting data to APIs, handling headers and authentication, or addressing CORS restrictions, the Fetch API provides a consistent and adaptable approach.
In this comprehensive guide, we covered the basics of using the Fetch API, making GET and POST requests, handling headers, and managing errors and status codes. We also explored advanced features like streaming responses and tracking request progress. Armed with this knowledge, you’ll be well-prepared to work with the Fetch API in different environments and develop robust web applications that interact seamlessly with external resources.
Remember to adapt your code to suit your specific needs and adhere to best practices in web development, such as making asynchronous requests and implementing proper error handling. The Fetch API is a valuable addition to your developer toolkit, and mastering it opens up endless possibilities for your projects. Whether you’re building web applications, server-side scripts, or exploring the capabilities of modern JavaScript runtimes, the Fetch API is a fundamental asset in your development journey.