KIEI-451: Introduction to Software Development

Javascript & the Browser - Part 2

Application Programming Interface (API)

Until now, most of our data has lived in our code. But more commonly, we need to fetch data from an external resource.

For example, if we want to display the current Bitcoin value (in USD) in our application, we could google the value and then put it in our code. It might look something like this:

  
  <div class="container">
    <div>
      <h1>Bitcoin Value</h1>
      <h2 id="currency">USD</h2>
      <p id="current">
        1 Bitcoin is 32198.48 USD
      </p>
    </div>
  </div>
  

Bitcoin Value

USD

1 Bitcoin is 32198.48 USD

The problem with this approach is that Bitcoin's value isn't static. It's always changing. So our frontend developers would have to be working around the clock, frantically updating the html with the most current weather data.

Instead, if we want to display real-time bitcoin data in our application, we can write code to request the data from an external resource at the very moment when we need it.

That external resource is known as an API (Application Programming Interface). APIs are like secret communication channels so applications can talk to each other in code.

APIs come in many different forms and often require registration for an API key - like a password. But for this lesson, we'll use a very simple API that has open/free access provided by Coindesk.

If interested, their API documentation (instructions written by developers for developers) can be found here. But the data we want can be found at the following URL. Note that USD can be replaced with other currencies (e.g. GBP or EUR).

  https://api.coindesk.com/v1/bpi/currentprice/USD.json

Try visiting the URL in your browser to see the data (it'll help to use a Chrome extension like JSONView or JSON Formatter). What does the data look like?

The opening { and closing } curly brackets indicate it's a javascript object. The technical name for it is JSON (Javascript Object Notation) which is a commonly used format for transmitting data across the internet. Many APIs like this one format their data as JSON for easy consumption. It's not technically a javascript object, but we'll see in a moment how it becomes one.

Seeing the data in the browser is useful for understanding its structure, but we actually need a way to read it with our code. Similar to how we access URLs via the browser, there's a built-in javascript function to access URLs in code:fetch().

There are several ways to use the fetch() function, but let's start with the most basic. It expects a string representing the API's URL (a.k.a. endpoint) as its first argument.

  fetch('https://api.coindesk.com/v1/bpi/currentprice/USD.json')

We're not quite ready to use it, though. Recall from the last lesson that some functions are asynchronous, meaning they will complete in their own time irrespective of any other code. fetch() is one of those functions, and for good reason.

Async / Await

When application code "talks" to another application via an API, it has no knowledge of how long it will take the other application to complete its job and return a response. APIs often respond very quickly, but not always and we can't expect that the response will be available immediately.

For example, maybe CoinDesk is experiencing an unusually high amount of traffic and their servers take a few seconds to build the response for our request. Because of that unknown timing, the following code which is trying to log the API response to the console won't work.

  
  let response = fetch('https://api.coindesk.com/v1/bpi/currentprice/USD.json')
  console.log(response)
  

If you try running that code, you might expect to see in the console the JSON data you saw when visiting that URL in the browser. But instead, you'll see something called a Promise. We won't be exploring what that is, but all you need to know is that the response isn't available yet when we try to display it with console.log().

In order to use the response from the API, we need to tell our code to wait for the fetch() function to finish. Doing so requires a tiny addition to our code:

  
  let response = await fetch('https://api.coindesk.com/v1/bpi/currentprice/USD.json')
  console.log(response)
  

By adding the keyword await, the code now knows it needs to pause and wait for the response before executing the next line.

The result is a Response object which has several attributes and methods:

That last bullet is a gotcha, though. Try it.

  
  let response = await fetch('https://api.coindesk.com/v1/bpi/currentprice/USD.json')
  let json = response.json()
  console.log(json)
  

Just like with fetch(), the value is a Promise which means it too is not available immediately. So, same as before, add the keyword await.

  
  let response = await fetch('https://api.coindesk.com/v1/bpi/currentprice/USD.json')
  let json = await response.json()
  console.log(json)
  
Result
  
  {
    time: {
      updated: "Jan 02, 2021 22:42:00 UTC"
      updatedISO: "2021-02-31T22:42:00+00:00"
      updateduk: "Jan 02, 2021 at 22:42 GMT"
    },
    disclaimer: "This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org",
    bpi: {
      USD: {
        code: "USD",
        rate: "32,198.4800",
        description: "United States Dollar",
        rate_float: 32198.48
      }
    }
  }
  
* Note the date and rate data above will be different. Also the currency will be different if replacing USD in the URL with a different currency.

And now, finally, the result is the data as seen from the browser.

We will most often use await in combination with fetch(). So the pattern will almost always be:

  
  let response = await fetch(url)
  let json = await response.json()
  

There's 1 other important modification: whenever using await within another function, that function needs to be an async function. It's easier to see it in use.

We'll build a tiny application that converts an amount of Bitcoin into USD (or any desired currency). To start, here's the HTML we'll use:

  
  <form class="bitcoin-form">
    <label for="amount">Enter amount of Bitcoin:</label>
    <input type="text" name="amount" id="amount">
    <button>Convert</button>
  </form>

  <div class="result"></div>
  

The HTML includes a <form> with an <input> and a <button> to submit the form. It also includes an empty <div>.

Next, let's add an event listener for when the form is submitted. For now, let's log the value the user enters into the form's amount input.

  
  document.querySelector('.bitcoin-form').addEventListener('submit', function(event) {
    event.preventDefault()
    let input = document.querySelector('#amount')
    console.log(input.value)
  })
  

We haven't used the submit event before. It works like any other event type except the listener is attached to the form. A click event on the button would also work.

To prevent the form from actually being submitted, we catch it first with preventDefault().

Next, it's time to get the current Bitcoin value from the API. And let's move it to a function while we're at it.

  
  async function getBitcoinValue() {
    let response = await fetch('https://api.coindesk.com/v1/bpi/currentprice/USD.json')
    let json = await response.json()
    console.log(json)
  }

  document.querySelector('.bitcoin-form').addEventListener('submit', function(event) {
    event.preventDefault()
    let input = document.querySelector('#amount')
    getBitcoinValue()
  })
  

Note the use of the async keyword when defining the function. Because getBitcoinValue() uses await, it also needs to have the async keyword. That's always the rule.

The last steps are to dig into the json data to find the rate_float and then display the calculated value in USD. (In order to perform a numeric calculation, we need to use the numeric value rate_float, not the string value rate).

  
  async function getBitcoinValue(amountOfBitcoin) {
    let response = await fetch('https://api.coindesk.com/v1/bpi/currentprice/USD.json')
    let json = await response.json()
    let rate = json.bpi.USD.rate_float
    let convertedAmount = amountOfBitcoin * rate
    document.querySelector('.result').innerHTML = `Your ${amountOfBitcoin} Bitcoin is worth ${convertedAmount} USD`
  }

  document.querySelector('.bitcoin-form').addEventListener('submit', function(event) {
    event.preventDefault()
    let input = document.querySelector('#amount')
    getBitcoinValue(input.value)
  })
  

The input value could be read within the getBitcoinValue function. Or it can be passed as an argument to the function as shown. Choosing to pass the value means the function can focus on the API request and the conversion and doesn't care where the original value comes from. Perhaps we want to reuse this function elsewhere. It might be easier to repurpose if it doesn't need to know where the value comes from. But either will have the same result, it's just a matter of preference and code design.

Below is the final HTML & Javascript code as well as a gif of the feature in use.

  
  <!-- HTML -->
  <form class="bitcoin-form">
    <label for="amount">Enter amount of Bitcoin:</label>
    <input type="text" name="amount" id="amount">
    <button>Convert</button>
  </form>

  <div class="result"></div>
  
  
  // Javascript

  async function getBitcoinValue(amountOfBitcoin) {
    let response = await fetch('https://api.coindesk.com/v1/bpi/currentprice/USD.json')
    let json = await response.json()
    let rate = json.bpi.USD.rate_float
    let convertedAmount = amountOfBitcoin * rate
    document.querySelector('.result').innerHTML = `Your ${amountOfBitcoin} Bitcoin is worth ${convertedAmount} USD`
  }

  document.querySelector('.bitcoin-form').addEventListener('submit', function(event) {
    event.preventDefault()
    let input = document.querySelector('#amount')
    getBitcoinValue(input.value)
  })
  
Result

Request Methods: GET & POST

When a user visits a URL in the browser (by typing it into the address bar or clicking a link), the request is known as a GET request. A GET request is the most common request made over the internet and usually refers to reading an application's content. But not all requests are simply reading data.

In an earlier lesson on forms, we discovered that when submitting a form, the form data is appended to the url with a ? and a query string (a collection of key-value pairs representing the form input names and user-entered data). That's fine in some cases, but often the data a user enters is private (think passwords or purchase info). In those situations, it's not very secure for that data to be exposed in the URL. So, instead of a GET request, when a user creates new data (e.g. posts a new instragam photo, submits an amazon order, adds a yelp review, etc), it's usually via a POST request.

These different requests are called HTTP methods. We won't spend much time on them right now, but it's important to know that sometimes when using fetch(), the API expects a different HTTP method. The Coindesk API endpoint is just a GET request, which makes sense since we're only reading data.

However, what if we wanted to save these calculated values to a database? Let's do just that.

We have created an Airtable spreadsheet to store these calculated Bitcoin conversions. The existing data can be seen here.

To post the calculated values, we can use the following JSON API endpoint:

  
  https://api.airtable.com/v0/applVUgpHTSjFnLMk/bitcoin_conversions
  

When making a POST request, the fetch() function expects a 2nd argument, which is a javascript object with several optional attributes, a few of which include:

For our API request, those attributes will have the following values:

Here's the code added to the getBitcoinValue() function:

  
  async function getBitcoinValue(amountOfBitcoin) {
    let response = await fetch('https://api.coindesk.com/v1/bpi/currentprice/USD.json')
    let json = await response.json()
    let rate = json.bpi.USD.rate_float
    let convertedAmount = amountOfBitcoin * rate
    document.querySelector('.result').innerHTML = `Your ${amountOfBitcoin} Bitcoin is worth ${convertedAmount} USD`

    // post convertedAmount to Airtable API
    let yourName = 'Grogu'
    let bodyData = {
      records: [
        fields: {
          bitcoin: amountOfBitcoin,
          value: convertedAmount,
          username: yourName
        }
      ]
    }
    await fetch('https://api.airtable.com/v0/applVUgpHTSjFnLMk/bitcoin_conversions', {
      method: 'POST',
      body: JSON.stringify(bodyData),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer key6kSCauSZNBpuUD'
      },
    })
  }
  

This is a bit of a contrived example, so we won't go through it in detail (we'll see more of relevant uses of POST in future lessons), but briefly:

If all goes well, after adding this code, when submitting the amount to convert to Bitcoin, a new record will be added to the Airtable database. To check, refresh the table here and scroll to the bottom to see if it was created.

Again, we'll learn more about using POST requests in future lessons.