A few weeks ago, my students started learning how to make HTTP requests with JavaScript using AJAX. As a developer with plenty of experience interacting with REST API’s on the Web, I wanted to make it easier for my students to ramp up on AJAX. As a result, I devised a five-step method and for the next few sections, walked them through how to use it. Then, I decided to take my own medicine and try my method. It turns out that this structured, detailed plan works out fairly well in a lot of situations!

I thus present to you, Geoffrey’s Five-Step Method for (mostly) Painless API Consumption. Each step is accompanied by an example of how the step can be applied.

1. Identify the necessary API endpoint(s)

Here is where you ask most of the questions about the API you are using. What are the URIs of the API endpoints that you need? What output does the endpoint produce, if any? What request method does the endpoint accept? Do you need to pass any parameters? If so, what are the names of the parameters, and their accepted values?

If you need to send any headers, what are they? Does the endpoint require any authentication? If so, what are the credentials? Last but not least, is there any additional data required by the endpoint?

Example: I am using Forecast.io to get weather data for my application. I’ll need the forecast at my current location for yesterday. Reading the Dark Sky Forecast API documentation, I see that the URI I need to hit is of the form:

https://api.forecast.io/forecast/APIKEY/LATITUDE,LONGITUDE,TIME

This is a GET request, requiring no parameters (but also accepting some optional parameters), no headers, and requiring no authentication (besides an API key in the URI). Reading the documentation for accepted values of APIKEY, LATITUDE, LONGITUDE, and TIME, I form a request with an arbitrarily chosen latitude, longitude, time, and include two optional GET parameters whose names and values I read from the documentation:

https://api.forecast.io/forecast/354f6cf4139dec73e7400d2949cc2ba5/78.2253966,15.4178498,1468713382?units=ca&exclude=minutely,hourly

Of course, the latitude and longitude in this situation will eventually depend on the user’s real-world physical location. This is handled in Step 5.

2. Check the API endpoint(s) in a browser or REST client

Now that you have identified the necessary endpoint(s), it’s time to actually see how the endpoint(s) respond.

To test REST APIs, I like to use a REST client whenever possible. A REST client is like a browser, but with extra features tailored for web development. Two of my favorite REST clients are Insomnia and Advanced Rest Client. Using your browser will also work for simple GET requests, as your browser is inherently a GET client.

For each endpoint, make sure it works as expected. If you expect an XML response, for example, and you receive something in plain text, then either the API is wrong or your assumption was wrong. In this case, you should revisit Step 1 and revise your assumptions about that specific API call.

If there is ever a chance that the API endpoint can fail, see if you can reproduce the failure case. This will be important in a later step, as good application design requires you to effectively communicate any errors to the user in a non-technical manner.

Example: Hitting the endpoint above, in my browser, yields the following application/json response:

{
    "latitude": 78.2253966,
    "longitude": 15.4178498,
    "timezone": "Arctic/Longyearbyen",
    "offset": 2,
    "currently": {
        "time": 1468713382,
        "summary": "Partly Cloudy",
        "icon": "partly-cloudy-day",
        "precipIntensity": 0.0305,
        "precipProbability": 0.02,
        "precipType": "rain",
        "temperature": 6.4,
        "apparentTemperature": 5.5,
        "dewPoint": 6.18,
        "humidity": 0.99,
        "windSpeed": 5.52,
        "windBearing": 236,
        "visibility": 8.42,
        "cloudCover": 0.54,
        "pressure": 1009.01,
        "ozone": 317.37
    },
    "daily": {
        "data": [{
            "time": 1468706400,
            "summary": "Partly cloudy throughout the day.",
            "icon": "partly-cloudy-day",
            "moonPhase": 0.41,
            "precipIntensity": 0.0279,
            "precipIntensityMax": 0.0762,
            "precipIntensityMaxTime": 1468724400,
            "precipProbability": 0.09,
            "precipType": "rain",
            "temperatureMin": 6.11,
            "temperatureMinTime": 1468724400,
            "temperatureMax": 9.52,
            "temperatureMaxTime": 1468778400,
            "apparentTemperatureMin": 5.49,
            "apparentTemperatureMinTime": 1468713600,
            "apparentTemperatureMax": 8.16,
            "apparentTemperatureMaxTime": 1468778400,
            "dewPoint": 7.55,
            "humidity": 0.99,
            "windSpeed": 3.25,
            "windBearing": 105,
            "visibility": 9.29,
            "cloudCover": 0.48,
            "pressure": 1009.26,
            "ozone": 311.25
        }]
    },
    "flags": {
        "sources": ["gfs", "cmc", "fnmoc", "metno_ne", "isd", "madis"],
        "metno-license": "Based on data from the Norwegian Meteorological Institute. (http://api.met.no/)",
        "isd-stations": ["010050-99999", "010070-99999", "010080-99999", "010170-99999", "201070-99999"],
        "madis-stations": ["ENSB"],
        "units": "ca"
    }
}

What could go wrong here? First of all, some of the forecast data may not be available. The currently object in the response may not exist, and daily.data could be an empty array. Secondly, the weather station might be offline, in which case a darksky-unavailable key will appear in flags (this is gleaned from the documentation). Last but not least, if your API key is on the free plan, you only get one thousand API calls per day. According to the developer dashboard, your access to the Forecast API will be “cut off” after the one thousandth call. This is a hint that you’ll have to handle this edge case in a later step!

3. Hit the API endpoint(s) in JavaScript

In the previous step, you used your browser or a REST client to check the API response. Now it’s time to write some JavaScript to make the request. I like to start with this basic skeleton for an AJAX request for pure JavaScript:

var ajax = new XMLHttpRequest();
ajax.onload = functionName;
ajax.onerror = errorFunctionName;
ajax.open("GET", "url", true);
ajax.send();
function functionName() {
    console.log(this); // log the response
    if (this.status == 200) { // request succeeded
        // do something with this.responseText;
    } else {
        // handle more HTTP response codes here;
    }
}
function errorFunctionName(e) {
    console.log(this);
    console.error(e);
    // do something with this.status, this.statusText
}

There are some replacements you will definitely want to make. First of all, substitute your API endpoint’s URI and request method in place of the "GET" and "url" placeholder strings. You should also give the onload and onerror handlers more descriptive names.

Depending on the complexity of the API with which you are interacting, you may need to send additional parameters, headers, or authentication. Feel free to add to the skeleton as necessary.

At this point, I don’t yet recommend writing any additional code in the onload and onerror handlers. Instead, notice the three console.* statements. Open up your browser, and see what is logged to the JavaScript console. Barring any AJAX errors, it should look like the response you received in Step 2. If not, go back a step, and check again.

If you’re using a library or framework such as jQuery or Angular, there should be documentation that explains how to write a basic Ajax request. Use that, instead of the skeleton code provided above.

Example: I will replace URL with the mock URI from Step 1. I will also rename the event handler functions. Other than that, there is nothing else to do but to run the code!

You may wonder “but what about substituting the user’s actual lat/long?” This is handled in Step 5; have patience young grasshopper.

4. Write the server response to the page

It is now time to fill in the function stubs for the onload and onerror events. You will replace all of the console.* statements with code to display the relevant data to the user. This step is very open-ended, and includes but is not limited to

  • Parsing/preprocessing the received data as necessary
  • Placing received data in the appropriate HTML/DOM element(s)
  • Formatting received data as necessary with JavaScript and/or CSS
  • Handling any possible server error(s) in a graceful manner

Exmaple: For the response above, I need to display the low temperature, high temperature, and the difference between the forecasted high/low and the temperature at the requested time. My first attempt to do that might look like this:

function functionName() {
    var json = JSON.parse(this.responseText);
    var currentTemp = json.currently.temperature;
    var forecastHi = json.daily.data[0].temperatureMin;
    var forecastLo = json.daily.data[0].temperatureMax;
    var diffHi = currentTemp - forecastHi;
    var diffLo = currentTemp - forecastLo;
    document.getElementById("hi-diff").innerHTML = diffHi + "°";
    document.getElementById("lo-diff").innerHTML = diffLo + "°";
}
function errorFunctionName(e) {
    window.alert("Could not retrieve yesterday's forecast. Please try again later.");
}

However, I soon realize after some testing and perusing through the documentation that certain errors must be handled. For instance, there may be a “temporary error (such as a radar station being down for maintenance)” or the daily forecast data might not be available at all. I make the following amendments to my onload handler:

if (this.status === 200) {
    var json = JSON.parse(this.responseText);
    if (json.flags.hasOwnProperty("darksky-unavailable")) {
        document.getElementById("error-flash").classList.add("shown");
        document.getElementById("error-flash").innerHTML = "The weather station is malfunctioning, please contact your local news channel for more info.";
    } else if (!json.hasOwnProperty("daily") || !json.daily.hasOwnProperty("data") || !(json.daily.data instanceof Array) || !json.daily.data.length) {
        document.getElementById("error-flash").classList.add("shown");
        document.getElementById("error-flash").innerHTML = "Could not get forecast data for yesterday.";
    } else {
        document.getElementById("error-flash").classList.remove("shown");
        // rest of code is as follows
    }
} else if (this.status === 400 || this.status === 403) {
    document.getElementById("error-flash").classList.add("shown");
    document.getElementById("error-flash").innerHTML = "Oh no, this service is too popular and is no longer available for the day!";
} else {
    document.getElementById("error-flash").classList.add("shown");
    document.getElementById("error-flash").innerHTML = "The weather service is down, blame the weatherman not me.";
}

5. Attach all necessary request data

Step 1 was all about coming up with an example, representative request to the API endpoint. Step 3 was all about coding up the request. Now it is time to remove the hard-coded values in the API call, and replace them with the appropriate internally- or externally-dependent values. Any details of the request that may change between different requests should be handled in this step. This includes parameters sent to the server, authentication usernames and passwords, and headers.

If the sending of your request depends on an event, such as a button click, handle the event in this step as well. This may be as simple as wrapping the AJAX code in a function, then assigning that function to the appropriate event.

Example: I need to get the actual location of the user and the current Unix time minus a day, instead of hard-coding the aforementioned parameters. After reading the JavaScript documentation on the Geolocation API and the Date object, my AJAX code looks like this:

if ("geolocation" in navigator) {
    navigator.geolocation.getCurrentPosition(function(position) {
        var yesterdayTime = Math.round((new Date()).getTime() / 1000) - 86400;
        var lat = position.coords.latitude;
        var long = position.coords.longitude;
        var ajax = new XMLHttpRequest();
        ajax.onload = functionName;
        ajax.onerror = errorFunctionName;
        ajax.open("GET", "https://api.forecast.io/forecast/354f6cf4139dec73e7400d2949cc2ba5/" + lat + "," + long + "," + yesterdayTime + "?units=ca&exclude=minutely,hourly", true);
        ajax.send();
    }, function() {
        alert("You must enable geolocation for the application to work!");
    });
} else {
    alert("Sorry, your system does not support geolocation.");
}

“Step 6”

As any respectable developer should do, clean up and optimize your code after you are done verifying your code!

Some disclaimers

In order for this method to work for you, you must constantly check that each step is working and correct, before moving on to the next one. If something fails, go back a step and fix it before moving on.

Why mostly painless? There’s only so much you can control, even if you are writing both the client and server code. Servers can be tricky beasts to handle, as they can go offline, throw rare but confusing errors, take forever to respond, and exhibit a wonderful spectrum of undocumented, migraine-inducing problems.

Published by Geoffrey Liu

A software engineer by trade and a classical musician at heart. Currently a software engineer at Groupon getting into iOS mobile development. Recently graduated from the University of Washington, with a degree in Computer Science and a minor in Music. Web development has been my passion for many years. I am also greatly interested in UI/UX design, teaching, cooking, biking, and collecting posters.

One thought on “A strategy for consuming REST API’s in JavaScript

  1. How would you enter in the header authentication perimeters.

    The API I’m trying to gain access to is http://apidocs.inspectlet.com the authentication looks like this.

    // prep the header we need to send for HTTP Basic Auth
    var username = “bob@example.com”;
    var apitoken = “af9f27cd9c02b54a9bead349662dddf4a7ea1430”;
    var headers = {“Authorization”: “Basic ” + btoa(username + “:” + apitoken)};

    If you could help me out it would be greatly appreciated.

    Feel free to shoot me an email with your response code

Leave a Reply