Sometimes it is very important to know how things are happening on the ground level. This article will talk about how to use XHR - XMLHttpRequest in vanilla JavaScript with the help of most loved REST API; JSONPlaceholder.
If you love web development you might have came across JavaScript's XHR at some point in your development career for AJAX. There are other easy and handy solutions out there to handle the AJAX calls like fetch() API or if using jQuery then ajax() method. The purpose of XHR was slightly different when the time it was developed as compared to today's development needs, so for current time the fetch API looks more relevant than XHR as it have many cool stuffs then XHR. Agreed, things are not that pretty with XHR in a long run and we end up in creating our own layer over XHR methods, but one must know at least the basics of XMLHttpRequest.
The XMLHttpRequest is used to bring food data from a web server. You can send or receive data back and forth from the web server under the hood (means without even refreshing the page). Such communication with the web server is termed as AJAX - Asynchronous JavaScript and XML; and XHR place the lead role for this.
In JavaScript the XMLHttpRequest is an object. It lives its life happily and die any ways just like most of the other JS objects. We can create an XHR as below:
const xhr = new XMLHttpRequest();
XHR has its own methods and properties to work with. As the communication with the web server will go through stages XHR provide event for each of them so that we can provide handlers for those events as per our needs.
Do not hesitate to make an AJAX call.
To start with using XHR we required some web server endpoint to send or receive data to and from. For that we will go with most popular fake REST API which is JSONPlaceholder. It provides end points to perform some of the most common REST operations such as GET, POST, PUT and DELETE. So lets get our hands dirty!
Lets first have a look at JSONPlaceholder API and what data it posses. The JSONPlaceholder API holds users, posts, comments, albums, photos and todos end points. These data have relation with one another, the posts, albums and todos are linked to user, photos are linked to album and comments are linked to posts.
users
|-- posts
|-- comments
|-- albums
|-- photos
|-- todos
The URL for these endpoints are as follows:
users:
https://jsonplaceholder.typicode.com/users
posts and comments:
https://jsonplaceholder.typicode.com/posts
https://jsonplaceholder.typicode.com/comments
albums and photos:
https://jsonplaceholder.typicode.com/albums
https://jsonplaceholder.typicode.com/photos
todos:
https://jsonplaceholder.typicode.com/todos
In order to get only one records we can pass the corresponding ids to the routes. Below will give record of user having userId as 1:
https://jsonplaceholder.typicode.com/users/1
Additionally, we can filter out relational data with help of query strings and nested routes. So if we want posts created by a user having userId 1, then we can use the following URL. Below will provide as an array of posts created by userId 1.
https://jsonplaceholder.typicode.com/posts?userId=1
This can also be achieved by using nested routes as shown below:
https://jsonplaceholder.typicode.com/users/1/posts
For more details about JSONPlaceholder click here to visit the website.
To make an AJAX call using XHR we have the decade old onreadystatechange property which is an event handler for the readyState property change event. Below code shows the usage of onreadystatechange:
const apiUrl = 'https://jsonplaceholder.typicode.com/posts';
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.open('GET', apiUrl, true);
xhr.send();
We can check how the onreadystatechange events can trigger on different stages of the call through below code:
const apiUrl = 'https://jsonplaceholder.typicode.com/posts';
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState === 0) {
console.log('request not initialized: ', this.readyState);
}
if (this.readyState === 1) {
console.log('server connection established: ', this.readyState);
}
if (this.readyState === 2) {
console.log('request received: ', this.readyState);
}
if (this.readyState === 3) {
console.log('processing request: ', this.readyState);
}
if (this.readyState === 4) {
console.log('request finished and response is ready: ', this.readyState);
}
};
xhr.open('GET', apiUrl, true);
xhr.send();
Initially the readyState is set to 0 and the onreadystatechange event gets fired when there is a change in readyState. So in the case where we are checking if the readyState is 0 will not be called and the output of the above code will be as follows:
server connection established: 1
request received: 2
processing request: 3
request finished and response is ready: 4
The status property of xhr returns the HTTP status code like 200, 400, 500 etc. A complete list of HTTP status codes and their meaning is listed here.
The XHR also holds properties those are event handlers for different events as the readyState change. These event properties are even more convenient than onreadystatechange where we do not have to manually check the readyState value. These properties are as follows. These event properties are actually part of XMLHttpRequestEventTarget interface. For more detail about XMLHttpRequestEventTarget click here.
onload
onerror
onprogress
onabort
We have created a project with basic CRUD operation using XHR and JSONPlaceholder API. Will focus on the XHR part of the project for CRUD operations. Let's perform each operation, using XHR object.
Will start with read operation using the HTTP's GET method. The following code will bring the data from the JSONPlaceholder API.
const apiUrl = 'https://jsonplaceholder.typicode.com/posts';
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status === 200) {
const posts = JSON.parse(xhr.responseText);
console.log(posts);
} else {
console.log('Server response:', xhr.status);
}
};
xhr.onerror = () => {
console.log('An error occurred, not able to process the request.');
};
xhr.open('GET', apiUrl, true);
xhr.send();
The onload() method is called when the response is received from the server. We additionally have to check the status of the response we received from the server and if it is 200 OK then it means we are able to proceed. Any other response will not provide the posts data. The data can be obtained using the responseText property, but it will not be in a JSON format. So we are using JSON.parse() method to convert it back to JSON.
NOTE: We are using the arrow function as an event handler for onload event, that's why we have to use xhr to obtain the xhr properties, if we want to make use of this keyword then we have to use the normal function instead of arrow function.
In order to create a post we must pass the post data as JSON object using an HTTP POST method. As per JSONPlaceholder API the post object must contain following properties to create a new post.
{
title: 'foo',
body: 'bar',
userId: 1
}
The response from JSONPlaceholder for this POST request will be a JSON object which looks like shown below. The id in the response will always be 101 and no real object is created at the API's end, this is just a fake response we get from JSONPlaceholder REST API with the response HTTP status as 201. We will not be able to use the newly generated post id to edit or delete operation as this resource is not actually present at the API's end, so it will not work with the new post id.
{
id: 101,
title: 'foo',
body: 'bar',
userId: 1
}
Let's check out the code logic for a POST call (create operation).
const apiUrl = 'https://jsonplaceholder.typicode.com/posts';
const data = JSON.stringify({
title: 'foo',
body: 'bar',
userId: 1
});
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status === 201) {
const post = JSON.parse(xhr.responseText);
console.log(post);
} else {
console.log('Server response:', xhr.status);
}
};
xhr.onerror = () => {
console.log('An error occurred, not able to process the request.');
};
xhr.open('POST', apiUrl, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(data);
The major difference between the GET and POST call is we pass something an object or any content to the xhr.send() method, also specify the type of content in the response header; as in the above code we are mentioning the Content-Type as application/json. One more important aspect is we must have to convert our JSON object to string (a textual content).
Most of the part of the PUT call will remains as POST call. The only difference it will have is the post id. Including the body object we have to pass the post id and also in the URL we have to append the post id at the end. It will return the same object which we have passed as request object. Lastly the HTTP method name will be PUT and the response status code will be 200. One more important point to notice is that we are passing 1 as post id in the url parameter of the open() method, the reason for that is the JSONPlaceholder will not update the record which is newly created as there will be no such object exist at the API's end. That is why we are passing the post id as 1 in both the places (in the body and URL). This is only to deal with JSONPlaceholder API in case if we build our own API this won't be an issue as we will have actual ids.
const apiUrl = 'https://jsonplaceholder.typicode.com/posts';
const data = JSON.stringify({
id: 1,
title: 'foo',
body: 'bar',
userId: 1
});
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status === 200) {
const post = JSON.parse(xhr.responseText);
console.log(post);
} else {
console.log('Server response:', xhr.status);
}
};
xhr.onerror = () => {
console.log('An error occurred, not able to process the request.');
};
xhr.open('PUT', `${apiUrl}/1`, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(data);
In case of DELETE it will return an empty object {} as response and the status code will be 200. There will be no issues of ids as there was with PUT, it works with any provided id.
const apiUrl = 'https://jsonplaceholder.typicode.com/posts';
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Server response:', xhr.status);
}
};
xhr.onerror = () => {
};
xhr.open('DELETE', `${apiUrl}/101`, true);
xhr.send();
Now this completes our CRUD operations using XHR with JSONPlaceholder API. We have created a SPA (Single Page Application) to do CRUD operation with posts of JSONPlaceholder API. Obviously the project also contains the UI manipulation code too, which is not mentioned in the above examples as this article is mainly focused on the XHR and not UI manipulations in JavaScript.
December 31, 2020
October 19, 2020
March 02, 2022