How to Execute an API Request in JavaScript

In this article, we will explore the process of making an API call in JavaScript. We have discussed various aspects of API calls, including what they are, using Postman to make API calls, and working with JSON. The skills and tools we have covered thus far are applicable to any programming language, be it Ruby, JavaScript, C#, or any other language. Regardless of the language, there are tools available for making and receiving requests and parsing different types of data returned by the API.

Now, we are ready to build a JavaScript application that can make an API call. There are multiple ways to achieve this, from using Web APIs to JavaScript libraries like jQuery, which is specifically designed for DOM manipulation and traversal. Additionally, there are numerous JavaScript tools available that simplify the process of making API calls by handling asynchrony. In this lesson, we will learn how to use the XMLHttpRequest object, which is a Web API, to make an API call. In the subsequent lessons, we will cover error handling and API key management.

In later lessons, we will explore alternative methods of making API calls, along with different tools for handling asynchronous code. It is important to note that APIs are asynchronous by nature. While we will only focus on applying async tools to API calls in this section, the techniques we learn can also be used for other asynchronous JavaScript operations.

Before we dive into more advanced async tools, we will start by learning the traditional method of making an API call using the XMLHttpRequest object. This approach is the foundation upon which other tools, such as jQuery’s AJAX and the Fetch API, are built. Although we will not be using the XMLHttpRequest object for the independent project in this section, understanding its functionality will provide a solid grasp of how tools like Fetch work when we use them later on.

Making an API Call in JavaScript

In this lesson, we will not include the complete code for building a webpack environment. However, we will provide a repository at the end of this three-part walkthrough that contains the code required to make an API call in a fully functional webpack environment. If you decide to code along with the upcoming three lessons, make sure to set up a webpack environment.

Make sure to complete the next lesson before making any commits or pushing your code to a remote repository if you choose to code along with this lesson.

For now, we will focus on two files: index.html and index.js.

Let’s start by examining the HTML code:

<html lang="en-US">
<head>
  <title>Weather</title>
</head>
<body>
  <div class="container">
    <h1>Get Weather Conditions From Anywhere!</h1>
    <p>To obtain the current weather conditions for a location, please enter the city name or the city and state separated by a comma. Here are three examples:</p>
    <pre>
      Portland
      Atlanta, Georgia
      Cairo
    </pre>
    <form>
      <label for="location">Enter a location:</label>
      <input id="location" type="text" name="location">
      <button type="submit" class="btn-success" id="weatherLocation">Get Current Temperature and Humidity</button>
    </form>
    <p id="showResponse"></p>
  </div>
</body>
</html>

The HTML includes instructions and an input field for entering a location. Additionally, we have a <p> tag with the id showResponse that will display the API response, which could be the temperature and humidity data or an error message.

To style the HTML, we are utilizing two Bootstrap classes: container and btn-success.

Now, let’s take a look at the JavaScript code responsible for the API call:

import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import './css/styles.css';

// Business Logic
function getWeather(city) {
  let request = new XMLHttpRequest();
  const url = `http://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=[YOUR-API-KEY-HERE]`;

  request.addEventListener("loadend", function() {
    const response = JSON.parse(this.responseText);
    if (this.status === 200) {
      printElements(response, city);
    }
  });

  request.open("GET", url, true);
  request.send();
}

// UI Logic
function printElements(apiResponse, city) {
  document.querySelector('#showResponse').innerText = `The humidity in ${city} is ${apiResponse.main.humidity}%. The temperature in Kelvins is ${apiResponse.main.temp} degrees.`;
}

function handleFormSubmission(event) {
  event.preventDefault();
  const city = document.querySelector('#location').value;
  document.querySelector('#location').value = null;
  getWeather(city);
}

window.addEventListener("load", function() {
  document.querySelector('form').addEventListener("submit", handleFormSubmission);
});

Our JavaScript code is divided into two parts: business logic and user interface logic. You may find most of the code familiar, except for the portion inside the getWeather function.

See also  JavaScript Development Tools

Let’s review the JavaScript code in the order it runs. At the bottom, we have two event listeners that are likely familiar to you:

window.addEventListener("load", function() {
  document.querySelector('form').addEventListener("submit", handleFormSubmission);
});

For the “submit” event listener on the form, we pass the handleFormSubmission function as a callback to be executed when the form is submitted. Recall that a callback function is a function passed as an argument to another function, to be executed at a later time.

Inside the handleFormSubmission function, we perform the following actions:

  • Prevent the default behavior of form submission.
  • Retrieve the value from the form input and save it in a variable called city.
  • Clear the value of the form input.
  • Call the getWeather function with city as an argument. The getWeather function handles making the API call to fetch the current weather data for the user-inputted city.

Now, let’s examine the getWeather function, which contains new and unfamiliar code. Note that this function belongs to the business logic section because it does not access or manipulate the DOM.

function getWeather(city) {
  let request = new XMLHttpRequest();
  const url = `http://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=[YOUR-API-KEY-HERE]`;

  request.addEventListener("loadend", function() {
    const response = JSON.parse(this.responseText);
    if (this.status === 200) {
      printElements(response, city);
    }
  });

  request.open("GET", url, true);
  request.send();
}

The code within the getWeather function is responsible for creating and sending the API request, as well as handling the response. Let’s break down this code step by step.

First, we use the XMLHttpRequest object constructor to create a new instance of it, which we save in a variable called request:

let request = new XMLHttpRequest();

The name XMLHttpRequest can be misleading, as these objects are used to interact with servers. This is exactly what we want to achieve with API calls. Although they were initially designed for XML requests, XMLHttpRequest objects can be used with other data formats, such as JSON. They are not limited to XML requests.

Next, we store the URL for our API call in a variable named url:

const url = `http://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=[YOUR-API-KEY-HERE]`;

We utilize a template literal to embed the city variable directly into the URL string, allowing the user’s input from the form to be passed into the URL.

Storing the request URL in a variable is not necessary, but it enhances code readability. We will use this URL as one of the three arguments passed to the following method later on: request.open("GET", url, true);. By directly including the URL as the second argument, the code becomes less legible: request.open("GET", http://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=[YOUR-API-KEY-HERE], true);.

Note that you must replace [YOUR-API-KEY-HERE] with your own API key for the code to function correctly. If you are following along with this lesson, feel free to add your API key now, but avoid committing your code until you learn how to secure your API key in the next lesson.

See also  Creating a JavaScript Contact Form to Send Emails

Afterward, we set up an event listener that listens for the completion of our API call:

request.addEventListener("loadend", function() {
  const response = JSON.parse(this.responseText);
  if (this.status === 200) {
    printElements(response, city);
  }
});

To achieve this, we attach an event listener to the XMLHttpRequest object’s loadend event. This event fires when a request (API call) has been completed, irrespective of whether it was successful or encountered an error.

Similar to other event listeners, the second argument is a callback function that runs when the corresponding event occurs. Therefore, when our API call is complete, this callback function will execute:

function() {
  const response = JSON.parse(this.responseText);
  if (this.status === 200) {
    printElements(response, city);
  }
}

This callback function performs three tasks:

  1. Parse the API response using JavaScript’s built-in JSON.parse() method. This ensures the data is correctly formatted as JSON.
  2. Check if the API call was successful by evaluating the HTTP status code of the response.
  3. If the API call was successful, call the printElements function to display the received data from the API.

Let’s now examine the printElements function:

function printElements(apiResponse, city) {
  document.querySelector('#showResponse').innerText = `The humidity in ${city} is ${apiResponse.main.humidity}%. The temperature in Kelvins is ${apiResponse.main.temp} degrees.`;
}

The purpose of the printElements function is to display the weather data on our webpage. Here, we utilize template literals to directly insert variables into strings.

To access the weather data, we use dot notation to access properties within the apiResponse parameter, which represents the API response object.

It is important to note that the structure of the data within the API response object varies across different APIs. That is why it is crucial to test API calls in applications like Postman, practice parsing JSON, and understand how to access specific data.

Let’s return to the getWeather function, as there are a couple of lines we haven’t covered yet:

request.open("GET", url, true);
request.send();

By the time we reach the last two lines of code in the getWeather function, we have performed several actions:

  • Created a new XMLHttpRequest object and saved it in the request variable.
  • Constructed the request URL using the user-inputted city data and the API key.
  • Added an event listener for the loadend event to handle the completion of our API call, enabling us to process the response.
  • Now, we need to send our request. The last two lines accomplish this: we open and send the request.

The XMLHttpRequest.open() method accepts three arguments:

  1. The method of the request (in this case, “GET”).
  2. The request URL (stored in the url variable).
  3. A boolean value indicating whether the request should be asynchronous.

Since we want the request to be asynchronous, meaning the browser does not freeze while waiting for the response, we set the third argument to true. In this section, these three arguments will remain the same for our API calls, except when explicitly making a “POST” request or a different type of request, which is not expected in this course section.

Once we have opened the request, we send it. At this point, we wait for the API to return a response. Once received, our callback function attached to the loadend event will execute. If the API call is successful, the printElements() function will be invoked.

Understanding XHR Objects

Let’s explore the XMLHttpRequest (XHR) objects in more detail. To examine the properties of an XMLHttpRequest object, we can add a breakpoint inside the conditional statement and run the code in the browser. Although it is not necessary at the moment, we recommend taking a closer look at an XMLHttpRequest object at some point.

See also  JavaScript for Data Analysis: Embracing Portability and Convenience

To add a breakpoint from the Sources tab in your browser is better than using the debugger; statement. The code snippet below indicates where the breakpoint should be placed:

request.addEventListener("loadend", function() {
  debugger;
  const response = JSON.parse(this.responseText);
  if (this.status === 200) {
    printElements(response, city);
  }
});

When paused at the breakpoint, we can navigate to the DevTools console and view this:

> this;
XMLHttpRequest {readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, onreadystatechange: ƒ, ...}

Expanding the XMLHttpRequest object in the DevTools console, it will resemble the following:

XMLHttpRequest object properties

We cropped the image for better visualization, but there is more information available for both the response and responseText properties. As seen, XMLHttpRequest objects possess a wide array of functionalities. Most of these properties are unnecessary to worry about at this point. However, a few of them are helpful within this section:

  • responseText: We previously discussed this property. It contains the text of the API response. However, you cannot access the data within this property using dot notation until it is parsed with JSON.parse().
  • status: This property corresponds to the HTTP status code of the API response. A code of 200 signifies a successful API call, but there are numerous other codes, such as 404 (not found) and so on. We will utilize the status property in future lessons.
  • statusText: In our case, it will display “OK” since we have a status code of 200. This status indicates that everything is functioning correctly. However, if something goes wrong, this property may provide a more detailed error message, such as “not found” or “not authorized.”

readyState

If we examine an XMLHttpRequest object, we will notice a property called readyState. In the image above, readyState is set to 4. While we will not utilize this property, understanding it can enhance our comprehension of XMLHttpRequest objects.

The readyState property is always a number, and each number represents a different state that our XMLHttpRequest object can assume.

Referencing the image from the MDN reference on XMLHttpRequest.readyState, we can observe that XMLHttpRequest objects have five possible states:

readyState Description
0 UNSENT – Client has been created
1 OPENED – open method has been invoked
2 HEADERS_RECEIVED – Server has been contacted and headers have been received
3 LOADING – Loading response data
4 DONE – API call completed

Each state describes a step in the process of making an API call using the XMLHttpRequest object. Once we reach state 4, “DONE”, we know the API call has been completed.

To visualize the change in readyState as we make an API call in our weather app, we can set up an event listener for the “readystatechange” event and log this.readyState inside it:

function getWeather(city) {
  let request = new XMLHttpRequest();
  // ...

  request.addEventListener("readystatechange", function() {
    console.log(this.readyState);
  });
}

Adding this event listener is optional, and we will not include it in the example repository.

Summary

In this lesson, we learned how to create an API call using the XMLHttpRequest object and parse the API response into JSON in order to access the data. We handled the asynchronous request by utilizing the “loadend” event listener, which fires when the API call is completed, regardless of success or failure.

Next, we will explore how to secure our API key(s) and add error handling to our OpenWeather app.