{"id":5674,"date":"2016-07-18T08:00:49","date_gmt":"2016-07-18T15:00:49","guid":{"rendered":"http:\/\/g-liu.com\/blog\/?p=5674"},"modified":"2016-07-18T11:53:34","modified_gmt":"2016-07-18T18:53:34","slug":"a-strategy-for-consuming-rest-apis-in-javascript","status":"publish","type":"post","link":"https:\/\/g-liu.com\/blog\/2016\/07\/a-strategy-for-consuming-rest-apis-in-javascript\/","title":{"rendered":"A strategy for consuming REST API&#8217;s in JavaScript"},"content":{"rendered":"<p>A few weeks ago, my students started learning&nbsp;how to make HTTP requests with JavaScript using AJAX.&nbsp;As a developer with plenty of experience interacting with REST API&#8217;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.&nbsp;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!<\/p>\n<p>I thus present to you, Geoffrey&#8217;s Five-Step Method for (mostly) Painless API Consumption. Each step is accompanied by an example of how the step can be applied.<\/p>\n<p><!--more--><\/p>\n<h1>1. Identify the necessary API endpoint(s)<\/h1>\n<p>Here is where you&nbsp;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?<\/p>\n<p>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?<\/p>\n<p><strong>Example<\/strong><strong>:<\/strong> I am using <a href=\"http:\/\/forecast.io\" target=\"_blank\">Forecast.io<\/a> to get weather data for my application. I&#8217;ll need the forecast at my current location for yesterday. Reading the <a href=\"https:\/\/developer.forecast.io\/docs\/v2\" target=\"_blank\" class=\"broken_link\" rel=\"nofollow\">Dark Sky Forecast API documentation<\/a>, I see that the URI I need to hit is of the form:<\/p>\n<pre>https:\/\/api.forecast.io\/forecast\/APIKEY\/LATITUDE,LONGITUDE,TIME\n<\/pre>\n<p>This is a GET request, requiring no&nbsp;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 <code>APIKEY<\/code>, <code>LATITUDE<\/code>, <code>LONGITUDE<\/code>, and <code>TIME<\/code>, 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:<\/p>\n<pre>https:\/\/api.forecast.io\/forecast\/354f6cf4139dec73e7400d2949cc2ba5\/78.2253966,15.4178498,1468713382?units=ca&exclude=minutely,hourly\n<\/pre>\n<p>Of course, the latitude and longitude in this situation will eventually depend on the user&#8217;s real-world physical location. This is handled in Step 5.<\/p>\n<h1>2. Check the API endpoint(s) in a browser or REST client<\/h1>\n<p>Now that you have identified the necessary endpoint(s), it&#8217;s time to actually see how the endpoint(s) respond.<\/p>\n<p>To test REST APIs,&nbsp;I like to use a REST client whenever possible. A REST client is like a browser, but&nbsp;with extra features tailored for&nbsp;web development. Two of my favorite REST clients are <a href=\"http:\/\/insomnia.rest\/\" target=\"_blank\">Insomnia<\/a> and <a href=\"https:\/\/chrome.google.com\/webstore\/detail\/advanced-rest-client\/hgmloofddffdnphfgcellkdfbfbjeloo\" target=\"_blank\">Advanced Rest Client<\/a>. Using your browser will also work for simple GET requests, as your browser is inherently a GET client.<\/p>\n<p>For each endpoint, make sure it works&nbsp;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.<\/p>\n<p>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.<\/p>\n<p><strong>Example:<\/strong> Hitting the endpoint above, in my browser, yields the following <code>application\/json<\/code> response:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n{\r\n    &quot;latitude&quot;: 78.2253966,\r\n    &quot;longitude&quot;: 15.4178498,\r\n    &quot;timezone&quot;: &quot;Arctic\/Longyearbyen&quot;,\r\n    &quot;offset&quot;: 2,\r\n    &quot;currently&quot;: {\r\n        &quot;time&quot;: 1468713382,\r\n        &quot;summary&quot;: &quot;Partly Cloudy&quot;,\r\n        &quot;icon&quot;: &quot;partly-cloudy-day&quot;,\r\n        &quot;precipIntensity&quot;: 0.0305,\r\n        &quot;precipProbability&quot;: 0.02,\r\n        &quot;precipType&quot;: &quot;rain&quot;,\r\n        &quot;temperature&quot;: 6.4,\r\n        &quot;apparentTemperature&quot;: 5.5,\r\n        &quot;dewPoint&quot;: 6.18,\r\n        &quot;humidity&quot;: 0.99,\r\n        &quot;windSpeed&quot;: 5.52,\r\n        &quot;windBearing&quot;: 236,\r\n        &quot;visibility&quot;: 8.42,\r\n        &quot;cloudCover&quot;: 0.54,\r\n        &quot;pressure&quot;: 1009.01,\r\n        &quot;ozone&quot;: 317.37\r\n    },\r\n    &quot;daily&quot;: {\r\n        &quot;data&quot;: &#x5B;{\r\n            &quot;time&quot;: 1468706400,\r\n            &quot;summary&quot;: &quot;Partly cloudy throughout the day.&quot;,\r\n            &quot;icon&quot;: &quot;partly-cloudy-day&quot;,\r\n            &quot;moonPhase&quot;: 0.41,\r\n            &quot;precipIntensity&quot;: 0.0279,\r\n            &quot;precipIntensityMax&quot;: 0.0762,\r\n            &quot;precipIntensityMaxTime&quot;: 1468724400,\r\n            &quot;precipProbability&quot;: 0.09,\r\n            &quot;precipType&quot;: &quot;rain&quot;,\r\n            &quot;temperatureMin&quot;: 6.11,\r\n            &quot;temperatureMinTime&quot;: 1468724400,\r\n            &quot;temperatureMax&quot;: 9.52,\r\n            &quot;temperatureMaxTime&quot;: 1468778400,\r\n            &quot;apparentTemperatureMin&quot;: 5.49,\r\n            &quot;apparentTemperatureMinTime&quot;: 1468713600,\r\n            &quot;apparentTemperatureMax&quot;: 8.16,\r\n            &quot;apparentTemperatureMaxTime&quot;: 1468778400,\r\n            &quot;dewPoint&quot;: 7.55,\r\n            &quot;humidity&quot;: 0.99,\r\n            &quot;windSpeed&quot;: 3.25,\r\n            &quot;windBearing&quot;: 105,\r\n            &quot;visibility&quot;: 9.29,\r\n            &quot;cloudCover&quot;: 0.48,\r\n            &quot;pressure&quot;: 1009.26,\r\n            &quot;ozone&quot;: 311.25\r\n        }]\r\n    },\r\n    &quot;flags&quot;: {\r\n        &quot;sources&quot;: &#x5B;&quot;gfs&quot;, &quot;cmc&quot;, &quot;fnmoc&quot;, &quot;metno_ne&quot;, &quot;isd&quot;, &quot;madis&quot;],\r\n        &quot;metno-license&quot;: &quot;Based on data from the Norwegian Meteorological Institute. (http:\/\/api.met.no\/)&quot;,\r\n        &quot;isd-stations&quot;: &#x5B;&quot;010050-99999&quot;, &quot;010070-99999&quot;, &quot;010080-99999&quot;, &quot;010170-99999&quot;, &quot;201070-99999&quot;],\r\n        &quot;madis-stations&quot;: &#x5B;&quot;ENSB&quot;],\r\n        &quot;units&quot;: &quot;ca&quot;\r\n    }\r\n}\r\n<\/pre>\n<p>What could go wrong here? First of all, some of the forecast data may not be available. The <code>currently<\/code> object in the response may not exist, and <code>daily.data<\/code> could be an empty array. Secondly, the weather station might be offline, in which case a <code>darksky-unavailable<\/code> key will appear in <code>flags<\/code> (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 <a href=\"https:\/\/developer.forecast.io\/\" target=\"_blank\" class=\"broken_link\" rel=\"nofollow\">developer dashboard<\/a>, your access to the Forecast API will be &#8220;cut off&#8221; after the one thousandth call. This is a hint that you&#8217;ll have to handle this edge case in a later step!<\/p>\n<h1>3. Hit the API endpoint(s) in JavaScript<\/h1>\n<p>In the previous step, you used your browser or a REST client to check the API response. Now it&#8217;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:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nvar ajax = new XMLHttpRequest();\r\najax.onload = functionName;\r\najax.onerror = errorFunctionName;\r\najax.open(&quot;GET&quot;, &quot;url&quot;, true);\r\najax.send();\r\n\r\nfunction functionName() {\r\n    console.log(this); \/\/ log the response\r\n    if (this.status == 200) { \/\/ request succeeded\r\n        \/\/ do something with this.responseText;\r\n    } else {\r\n        \/\/ handle more HTTP response codes here;\r\n    }\r\n}\r\n\r\nfunction errorFunctionName(e) {\r\n    console.log(this);\r\n    console.error(e);\r\n    \/\/ do something with this.status, this.statusText\r\n}\r\n<\/pre>\n<p>There are some replacements you will definitely want to make. First of all, substitute your API endpoint&#8217;s URI and request method in place of the <code>\"GET\"<\/code> and <code>\"url\"<\/code> placeholder strings. You should also give the <code>onload<\/code> and <code>onerror<\/code> handlers more descriptive names.<\/p>\n<p>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.<\/p>\n<p>At this point, I don&#8217;t yet recommend writing any additional code in the <code>onload<\/code> and <code>onerror<\/code> handlers. Instead, notice the three <code>console.*<\/code> 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.<\/p>\n<p>If you&#8217;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.<\/p>\n<p><strong>Example:<\/strong> I will replace <code>URL<\/code> 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!<\/p>\n<p>You may wonder &#8220;but what about substituting the user&#8217;s actual lat\/long?&#8221; This is handled in Step 5; have patience young grasshopper.<\/p>\n<h1>4. Write the server response to the page<\/h1>\n<p>It is now time to&nbsp;fill in the function stubs for the <code>onload<\/code> and <code>onerror<\/code> events.&nbsp;You will replace all of the <code>console.*<\/code> statements with code to display the relevant data to the user. This step is very open-ended, and includes but is not limited to<\/p>\n<ul>\n<li>Parsing\/preprocessing the received data as necessary<\/li>\n<li>Placing received data in the appropriate HTML\/DOM element(s)<\/li>\n<li>Formatting received data as necessary with JavaScript and\/or CSS<\/li>\n<li>Handling any possible server error(s) in a graceful manner<\/li>\n<\/ul>\n<p><strong>Exmaple:<\/strong> 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:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nfunction functionName() {\r\n    var json = JSON.parse(this.responseText);\r\n    var currentTemp = json.currently.temperature;\r\n    var forecastHi = json.daily.data&#x5B;0].temperatureMin;\r\n    var forecastLo = json.daily.data&#x5B;0].temperatureMax;\r\n\r\n    var diffHi = currentTemp - forecastHi;\r\n    var diffLo = currentTemp - forecastLo;\r\n\r\n    document.getElementById(&quot;hi-diff&quot;).innerHTML = diffHi + &quot;&amp;deg;&quot;;\r\n    document.getElementById(&quot;lo-diff&quot;).innerHTML = diffLo + &quot;&amp;deg;&quot;;\r\n}\r\n\r\nfunction errorFunctionName(e) {\r\n    window.alert(&quot;Could not retrieve yesterday's forecast. Please try again later.&quot;);\r\n}\r\n<\/pre>\n<p>However, I soon realize after some testing and perusing through the documentation that certain errors must be handled. For instance, there may be a &#8220;temporary error (such as a radar station being down for maintenance)&#8221; or the daily forecast data might not be available at all. I make the following amendments to my <code>onload<\/code> handler:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nif (this.status === 200) {\r\n    var json = JSON.parse(this.responseText);\r\n    if (json.flags.hasOwnProperty(&quot;darksky-unavailable&quot;)) {\r\n        document.getElementById(&quot;error-flash&quot;).classList.add(&quot;shown&quot;);\r\n        document.getElementById(&quot;error-flash&quot;).innerHTML = &quot;The weather station is malfunctioning, please contact your local news channel for more info.&quot;;\r\n    } else if (!json.hasOwnProperty(&quot;daily&quot;) || !json.daily.hasOwnProperty(&quot;data&quot;) || !(json.daily.data instanceof Array) || !json.daily.data.length) {\r\n        document.getElementById(&quot;error-flash&quot;).classList.add(&quot;shown&quot;);\r\n        document.getElementById(&quot;error-flash&quot;).innerHTML = &quot;Could not get forecast data for yesterday.&quot;;\r\n    } else {\r\n        document.getElementById(&quot;error-flash&quot;).classList.remove(&quot;shown&quot;);\r\n        \/\/ rest of code is as follows\r\n    }\r\n} else if (this.status === 400 || this.status === 403) {\r\n    document.getElementById(&quot;error-flash&quot;).classList.add(&quot;shown&quot;);\r\n    document.getElementById(&quot;error-flash&quot;).innerHTML = &quot;Oh no, this service is too popular and is no longer available for the day!&quot;;\r\n} else {\r\n    document.getElementById(&quot;error-flash&quot;).classList.add(&quot;shown&quot;);\r\n    document.getElementById(&quot;error-flash&quot;).innerHTML = &quot;The weather service is down, blame the weatherman not me.&quot;;\r\n}\r\n<\/pre>\n<h1>5. Attach all necessary request data<\/h1>\n<p>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&nbsp;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.<\/p>\n<p>If the sending of your request depends on an event, such as a button click,&nbsp;handle&nbsp;the event&nbsp;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.<\/p>\n<p><strong>Example:<\/strong> I need to get the actual location of the user and the current Unix time minus a&nbsp;day, instead of hard-coding the&nbsp;aforementioned parameters. After reading the JavaScript documentation on the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Geolocation\/Using_geolocation\">Geolocation API<\/a> and the <a href=\"http:\/\/www.electrictoolbox.com\/unix-timestamp-javascript\/\" class=\"broken_link\" rel=\"nofollow\">Date object<\/a>, my AJAX code looks like this:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nif (&quot;geolocation&quot; in navigator) {\r\n    navigator.geolocation.getCurrentPosition(function(position) {\r\n        var yesterdayTime = Math.round((new Date()).getTime() \/ 1000) - 86400;\r\n        var lat = position.coords.latitude;\r\n        var long = position.coords.longitude;\r\n\r\n        var ajax = new XMLHttpRequest();\r\n        ajax.onload = functionName;\r\n        ajax.onerror = errorFunctionName;\r\n        ajax.open(&quot;GET&quot;, &quot;https:\/\/api.forecast.io\/forecast\/354f6cf4139dec73e7400d2949cc2ba5\/&quot; + lat + &quot;,&quot; + long + &quot;,&quot; + yesterdayTime + &quot;?units=ca&amp;exclude=minutely,hourly&quot;, true);\r\n        ajax.send();\r\n    }, function() {\r\n        alert(&quot;You must enable geolocation for the application to work!&quot;);\r\n    });\r\n} else {\r\n    alert(&quot;Sorry, your system does not support geolocation.&quot;);\r\n}\r\n<\/pre>\n<h1>&#8220;Step 6&#8221;<\/h1>\n<p>As any respectable developer should do, clean up and optimize your code after you are done verifying your code!<\/p>\n<h1>Some disclaimers<\/h1>\n<p>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.<\/p>\n<p>Why <em>mostly<\/em> painless? There&#8217;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.<\/p>\n<!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons generic via filter on the_content --><!-- AddThis Related Posts generic via filter on the_content -->","protected":false},"excerpt":{"rendered":"<p>A few weeks ago, my students started learning&nbsp;how to make HTTP requests with JavaScript using AJAX.&nbsp;As a developer with plenty of experience interacting with REST API&#8217;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 &#8230;<!-- AddThis Advanced Settings generic via filter on wp_trim_excerpt --><!-- AddThis Share Buttons generic via filter on wp_trim_excerpt --><!-- AddThis Related Posts generic via filter on wp_trim_excerpt --><\/p>\n","protected":false},"author":2,"featured_media":5773,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"footnotes":"","jetpack_publicize_message":"A strategy for consuming REST API's in #JavaScript #ajax","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[830],"tags":[],"jetpack_publicize_connections":[],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/g-liu.com\/blog\/wp-content\/uploads\/2016\/07\/Giant_Panda_Eating.jpg","jetpack_shortlink":"https:\/\/wp.me\/p2Zt3y-1tw","_links":{"self":[{"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/posts\/5674"}],"collection":[{"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/comments?post=5674"}],"version-history":[{"count":10,"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/posts\/5674\/revisions"}],"predecessor-version":[{"id":5777,"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/posts\/5674\/revisions\/5777"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/media\/5773"}],"wp:attachment":[{"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/media?parent=5674"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/categories?post=5674"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/g-liu.com\/blog\/wp-json\/wp\/v2\/tags?post=5674"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}