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:

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

  1. We use the fetch function, passing the URL of the resource we want to fetch as an argument.
  2. The fetch function returns a Promise that resolves to a Response object representing the response to the request.
  3. We call .json() on the response to parse the JSON data.
  4. 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:

bash
npm install node-fetch

Now, let’s see an example of using the Fetch API in a Node.js environment:

javascript

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:

javascript
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

javascript
// 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

javascript
// 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

javascript
// 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

javascript
// 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:

javascript
// 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:

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

javascript
// 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:

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

javascript

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

javascript
// 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

javascript
// 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.