Using fetch to Send HTTP Requests in JavaScript

Introduction

JavaScript’s Fetch API allows us to send HTTP requests. It’s been a standard part of JavaScript since ECMAScript 2015 (commonly known as ES6) was introduced and uses Promises.

This article will first show you how requests were made with vanilla JavaScript before the Fetch API was developed. We will then guide you on how to use the Fetch API, highlighting how much of an improvement it is over other methods.

Setup

This article looks at using the Fetch API to make HTTP requests in the browser. As such, we need to set up an HTML page that our browser can display. In your workspace, start by creating an index.html file.

The index.html file will be used throughout the article. The HTML page has no textual content, it will only be used to load the JS files so we can see the requests and responses in our browser’s console:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>JavaScript HTTP Requests</title>
</head>

<body>
    <script src="./xhr.js"></script>
</body>

</html>

We’ll change the script tag when we are learning new topics, but the rest of the HTML will stay the same.

You also want to have your browser console open so we can see the results of our HTTP requests. This is usually done by right-clicking on the webpage and selecting "Inspect". On Chrome it looks like this:

The "Inspect" menu item when right-clicking in Chrome

Now, let’s select the "Console" tab so we can see any output that our JavaScript logs:

The "Console" tab in our Developer Console

You’re all set! Let’s begin sending HTTP requests with the first method possible with JavaScript – XMLHttpRequest.

Requests with XMLHttpRequest

Before the Fetch API existed, all JavaScript requests were done with an XMLHttpRequest (or XHR) object. Despite its name, this object can retrieve data in any format from a server. It’s not just limited to XML.

Let’s get hands on with an XHR request in our browser. In the same folder as your index.html file, create a new xhr.js file.

This new JavaScript file will create an XHR object and send a GET request to a JSON API. We will then log the results of the request in the console. In your xhr.js file, enter the following:

let xhr = new XMLHttpRequest();
xhr.open('get', 'https://jsonplaceholder.typicode.com/posts/1');
xhr.send();

xhr.onload = function() {
    console.log(xhr.response);
};

In the first line, we created a new XMLHttpRequest object. We then used the open() method to create a new HTTP request. The first argument of open() is the HTTP method of the request – in this case, we are sending a GET request. The second argument is the URL with the server resource we want. We then use the send() method to dispatch the request.

When an XHR successfully gets data from the network, it sends a load event. To process the data after it’s loaded, we set a function to the onload property of the XHR object. In this case, we simply log the response to the console.

Now, in your developer console you should see the following.

JSON output of the XHR request for blog posts

Good job on making an API request with XMLHttpRequest!

While serviceable, the way it handles asynchronous data is very different from the organized and standardized Promises used in modern JavaScript. We can maintain easier code with the Fetch API.

The Fetch API

The Fetch API is a promise-based API for making HTTP requests, similar to what we did using XMLHttpRequest. Unlike XMLHttpRequest we do not have to create new objects when using the Fetch API. Browsers come with a global fetch() function that we can use to make requests.

Let’s see how we can use this API to make HTTP requests over the Internet.

Sending Requests with Fetch

The Fetch API can make GET, POST, PUT, PATCH, DELETE and other kinds of HTTP requests. We’ll focus on two of the most common methods used in HTTP requests: GET and POST.

GET Requests

Let’s use the Fetch API to make a GET request to https://jsonplaceholder.typicode.com/posts/1 like we did with XMLHttpRequest earlier.

In your index.html file, change the script tag to reference a new JavaScript file:

<script src="./fetchGet.js"></script>

Now create the new fetchGet.js file in the same workspace. We’ll be sending a GET request and logging the output to the console once again. Enter the following code in fetchGet.js:

fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => response.json())
    .then(json => console.log(json));

In the first line we use the global fetch() function to send a GET request to our API. The argument of fetch() is the URL with the server-side resource.

We then chain the promise with the then() method, which captures the HTTP response in the response argument and call its json() method. The json() method parses the response body to a JSON object. However, it returns that as a promise.

That’s we why use then() once again to chain another promise, which logs the parsed JSON to the console.

Reload the index.html if needed so you can see the following output:

JSON output of the fetch request for blog posts

Note: The output would look different to what we got when we made the GET request with XMLHttpRequest. That’s because XMLHttpRequest returns the HTTP response data as a string, whereas we parsed the data to a JSON object. While the returned formats are different, their contents are the same.

Let’s see how we can use fetch() to send data in a POST request.

POST Requests

We can upload data with fetch() by adding a JavaScript object as its second argument with the required information to send the HTTP request.

Let’s use fetch() to upload JSON data in POST request to a mock API. In your index.html file, change the script tag to reference a new JavaScript file:

<script src="./fetchPost.js"></script>

Now create fetchPost.js in your workspace so we can make a POST request to the API that will upload a new to-do item as a JSON object. Type the code below in fetchPost.js:

const todo = {
    title: 'Some really important work to finish'
};

fetch('https://jsonplaceholder.typicode.com/todos', {
        method: 'POST',
        body: JSON.stringify(todo),
        headers: {
            'Content-type': 'application/json; charset=UTF-8'
        }
    })
    .then(response => response.json())
    .then(json => {
        console.log(json);
    });

The first thing we do is create a todo object, which contains the data we’d like to send to the API.

As with GET requests, we use fetch() by providing a URL of the API we want to reach. However, this time we have an object as a second argument to fetch() with the following properties:

  • method: A string that specifies with HTTP method to use in the request
  • body: A string with any data we want to give to the server in our request
  • headers: An object that allows us to add any headers we want our HTTP requests to include

As with the GET request, we process the server’s response as JSON and log it to the developer console. Reloading our index.html should show us the following console output:

JSON output of the fetch POST request to add a new "todo" item

Great job using fetch() to upload data via POST request!

Now that we have a handle on making various HTTP requests with the Fetch API, let’s see how we can handle different HTTP responses.

Processing Responses with Fetch

So far, we’ve been parsing the response data to JSON. While this works with the API used in the example, other response may return different types of non-JSON data.

An HTTP response object that’s returned after a successful fetch() requests can be parsed to various formats. In addition to the json() method, we can use the following:

  • text(): Returns the response as string data
  • blob(): Returns the response as blob object (binary data along with its encoding)
  • formData(): Return the response as FormData object (which stores key-value pairs of string data)
  • arrayBuffer(): Return the response as ArrayBuffer (low-level representation of binary data)

Like the json() method, these functions return a promise with the content. Therefore, they must all be chained with a then() function so the content can be processed.

These functions are used to process successful HTTP responses that return data. Let’s now have a look and how we can handle errors with the Fetch API.

Handling HTTP Errors

As with any other promise, fetch() errors are handled in the catch() method that’s placed at the end of a promise chain. However, the catch() function is only used if fetch() could not send a request. This typically means that there was a network error.

If we try to access a URL that does not exist and the server returns a 404, it would not be caught in the catch() method, as 404 is a valid HTTP response status.

Therefore, when handling errors with the Fetch API we need to do two things:

  • Include the catch() clause at the end of the promise chain to pick up any network errors
  • Check the HTTP status code of the response to see if it was successful or not.

Let’s do another example where we try to get a URL that does not exist.

Using our GET request example, we can use catch() like this:

fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => response.json())
    .then(json => console.log(json))
    .catch(err => console.error(err));

However, the catch() function is only used if the fetch() request could not be sent. In your index.html file, change the script tag to reference a new JavaScript file:

<script src="./fetchError.js"></script>

Now in your workspace create a new fetchError.js file. Enter the following code:

fetch("https://jsonplaceholder.typicode.com/notreal/")
    .then(response => {
        if (!response.ok) {
            throw new Error("Could not reach website.");
        }
        return response.json();
    })
    .then(json => console.log(json))
    .catch(err => console.error(err));

We begin by sending a GET request to a non-existent URL on that API. Note the change in the first then() function which parses the response body to JSON:

if (!response.ok) {
    throw new Error("Could not reach website.");
}

We check the ok property, which is boolean. It is true if the HTTP status code of the response is between 200-299. By using the not operator (!), we can capture the cases where the server returned an HTTP error. If we do get an HTTP error, we throw a custom error which would end the fetch() promise chain.

If we did not receive an HTTP error, we return the JSON response as a promise, like before.

At the end of the promise chain, we have a catch() function, which simply logs the error to the console.

If you reload your index.html page, you should see this console output:

Error output trying to reach a fake URL

Well done, you covered the fundamentals of the Fetch API.

Conclusion

The Fetch API provides a promise-based way to send HTTP requests in JavaScript. Because it is promise-based, developers see it as a cleaner replacement to XMLHttpRequest.

With the fetch() function, we can make GET and POST requests to different URLs. We can configure a fetch() requests to use any HTTP method we want to use.

The fetch() function also provides a response object that can be parsed into various formats. These include JSON, text, and bytes to name a few.

We also saw how we can handle errors when making requests with fetch(). Aside from putting the catch() method at the end of the promise chain to pick up network errors, we should also check the status code of the HTTP response we received before parsing its data.

The Fetch API makes external API calls manageable without the use of external libraries. What APIs are you planning to access with fetch()?


Source: Stack Abuse