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 this 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 as we're working through new topics, but the rest of the HTML will stay the same.
You will 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 web page and selecting "Inspect". On Chrome it looks like this:
Now, let's select the "Console" tab so we can see any output that our JavaScript logs:
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.
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 calls its json()
method. The json()
method parses the response body to a JSON object. However, it returns that as a promise.
That's why we 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:
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 a POST
request to a mock API. In your index.html
file, change the script
tag to reference a new JavaScript file:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
<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 requestbody
: A string with any data we want to give to the server in our requestheaders
: 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:
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 responses may return different types of non-JSON data.
An HTTP response object that's returned after a successful fetch()
request can be parsed to various formats. In addition to the json()
method, we can use the following:
text()
: Returns the response as string datablob()
: Returns the response as blob object (binary data along with its encoding)formData()
: Return the response asFormData
object (which stores key-value pairs of string data)arrayBuffer()
: Return the response asArrayBuffer
(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:
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()
request 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()
?