Consuming web APIs

There are countless thousands of APIs provided on the web. Fifteen percent of them are public*. A web application can consume private ones once you have signed up with the owner of the API and been given client API keys and secrets.

Either way, PicoStack is an easy way to consume these APIs.

In this post, we'll walk through using one particular public API, which provides information about events held at Brigham Young University (BYU).

Finding a public API

Knowing that someone at BYU must be keeping track of events, we did a web search for "byu calendar event api", and found that there is a public API provided by BYU and that it is documented on their Events API page.

There is a single API endpoint, which can provide either information on all events, or filter them by category and, optionally, by date range.

Using the public BYU events API

Globally within a ruleset, we bind the name api_url to the base URL for the API. For simplicity, we'll not use a date range.

  global {
    api_url = "https://calendar.byu.edu/api/Events.json?categories="
  }

Getting the categories

Since there isn't a legend for the category identifiers in the documentation, we'll use a rule to infer those from the data for all events:

  rule inferCategoryIds {
    select when byu_events factory_reset
    pre {
      all_events = http:get(api_url+"all"){"content"}.decode()
      accumulate = function(ans,ev){
        cat_name = ev{"CategoryName"}
        arr = ans.get(cat_name).defaultsTo([])
        ans.put(cat_name,arr.union(ev{"CategoryId"}))
      }
      starting_with = ent:categories.defaultsTo({})
      categories = all_events.reduce(accumulate,starting_with)
    }
    fired {
      ent:categories := categories
    }
  }

The inferCategoryIds rule selects on the byu_events:factory_reset event, which is raised when the ruleset containing this rule is installed in a pico.

The heart of the work is actually calling the API, which is done by the built-in http:get function. It takes the complete API URL as the first parameter and returns the HTTP response. We are interested in the content of that response. Since we use the JSON version of the API, the content of the response is a string which is a valid JSON object. By using the decode() operator, we get a KRL array of maps, one for each of the events.

It is then a simple matter to reduce that array into a categories map. We chose to use the name of the category as the key in that map, and a set of category identifiers that appear for each category name. The reduce() operator works by accumulating an answer starting with some value, and running the function for each element of the array to update the accumulator. Our accumulator in this case is a map, whose key will be a category name and whose value will be a set** of category ids, as seen in the array we are reducing.

Displaying the categories as a selection list

We define and share a function to generate an HTML page which shows a form with a selection list of categories:

    categories = function(_headers){
      html:header("select from categories","",null,null,_headers)
      + <<
<h1>Select from categories</h1>
<form action="category.html">
<select name="category_id" required>
<option value="">Choose a category:</option>
#{ent:categories.map(function(v,k){
  <<  <option value="#{v.head()}">#{k}</option>
>>
}).values().join("")}
</select>
<button type="submit">See events</button>
</form>
>>
      + html:footer()
    }

This page is primarily a form which when submitted will link to another page to be generated below.

The trick of using the required attribute in the select tag, along with the first option having a blank value will have the browser require a selection on the part of a visitor to this page.

A loop (the map() operator) over the ent:categories will generate an option tag for each one, displaying the category name and using the first category id for that name as the value.

Having made their selection, a visitor will click the "See events" button and we'll display the events in the selected category.

What a visitor to our page sees

Having selected the "Other" category, our visitor will then click the "See events" button.

Displaying events for a chosen category

This function generates an HTML page for a given category id:

    category = function(category_id,_headers){
      events = http:get(api_url+category_id){"content"}.decode()
      html:header("byu events by category","",null,null,_headers)
      + <<
<h1>BYU events</h1>
<h2>Category: #{nameFromId(category_id)}</h2>
<dl>
#{events.map(function(v){
  all_day = v{"AllDay"}.decode()
  start_dt = v{"StartDateTime"}
  full_url = v{"FullUrl"}
  <<<dt>#{all_day => start_dt.split(" ").head() | start_dt}</dt>
<dd><a href="#{full_url}">#{v{"Title"}}</a></dd>
>>
}).join("")}</dl>
>>
      + html:footer()
    }

Here, we call the API, passing in the selected category id, to get all the events in that category.

In the HTML page, we display the category name, and then an HTML dictionary list showing the date (and time if not an all-day event) and then the event title. The event title is hyperlinked to the full URL of a page describing that event (this latter page is provided by BYU).

What a visitor to our page sees


Here they see all of the events for the "Other" category.

Putting it all together

To see this web application in action, you'll need to do these things: 

  1. have control of a server (either on the Internet, or just your local machine)
  2. install the pico engine on that machine (see instructions)
  3. start the developer UI
  4. create a pico (or just use the Root pico)
  5. using the Rulesets tab, install a simple html module
  6. install the full byu.events ruleset
  7. using the Channels tab, find the channel tagged byu_events (double-click on its id and then copy that)
  8. from your browser visit the resource described by a URL following the (/sky/cloud) pattern and tailored to your pico engine's domain and port and the identifier of the channel you found in step 7

Conclusion

We have shown in this post how to create a nine page website (one page for the category list and one for each of the categories) using a single ruleset. Install this ruleset in any pico, and you'll have the entire website.

In addition to the pages we supply, there are the links to the pages BYU supplies to describe each of the many events.

Notes

* according to A Deep Dive into Postman's 2021 State of the API Report, retrieved Sept. 8, 2022

** we achieve a set by using the union() operator against the array we are accumulating (instead of the usual append() operator for arrays)

No comments:

Post a Comment