Fetch Data from an API with JavaScript

Fetch Data from an API with JavaScript

Dad jokes are way funnier when you can build an app with them

ยท

7 min read

This TinyApp is DadJoke Central. It displays dad jokes on a web page. The TinyApp is powered by a free API called ICanHazDadJoke. While this is all fun, this TinyApp shows how a JavaScript frontend can communicate with a backend -- pretty powerful stuff!

Meet DadJoke Central

This TinyApp comprises of just an index.html, styles.css, and index.js. The HTML and CSS is very simple, and most of the fun happens in the JavaScript. Here's the sandbox:

Structure and Style

HTML provides structure to our application, and CSS gives it a nice coat of paint. Starting with the HTML, we see a title, subtitle, a button, and a placeholder for where the jokes are generated.

<h1>๐Ÿ˜‚ DadJoke Central ๐Ÿ˜‚</h1>
<p>The TinyApp for Dad Jokes.</p>
<button id="jokeButton">Get a Joke!</button>
<p id="joke">~ a joke will be here ~</p>

The important elements to take note of in this HTML are:

  • the button with an id of jokeButton. We'll use this button to trigger the function which grabs our dad joke.
  • the paragraph tag with an id of joke. We'll look to update the content of this paragraph with a joke that we retrieve from the API.

Our styling is fairly simple too. It's mostly adding some color and fonts, so I won't break it down, but I'll include it here:

body {
  font-family: "Arbutus Slab", serif;
  color: #4a4a4a;
  background: #eee;
}

#joke {
  color: darkblue;
  font-size: 1.2em;
  font-weight: bold;
}

Let's break down what happens next, and talk about APIs!

What does an API do?

Why are APIs important, and why do we hear about them everywhere? APIs allow developers to have their own application talk to other applications that they haven't written. It makes building web apps modular. When we talk about web APIs, usually that means that some data is being sent to a frontend or another backend.

In this TinyApp, we're using ICanHazDadJoke, which is an application that serves data in the JSON format. A JSON API is almost the de-facto way that modern web applications talk to one another. This is because most languages can understand and parse JSON. You can almost imagine it as a translation bridge between a frontend and a backend.

In a sense, this TinyApp is a frontend that uses ICanHazDadJoke as a backend; it provides the data we're looking to display.

Fetch and Asynchronous JavaScript

To grab a joke, we need to make a request to the ICanHazDadJoke API. We can do this using Fetch in JavaScript. The following code looks dense and scary, but take a look and see if you can figure out what's going on! I'll break it down below.

const fetchJoke = async () => {
  try {
    const response = await fetch("https://icanhazdadjoke.com/", {
      headers: {
        Accept: "application/json"
      }
    });

    if (!response.ok) {
      const message = `Error: ${response.status}, ${response.statusText}`;
      const error = new Error(message);
      throw error;
    }

    const joke = await response.json();
    return joke;
  } catch (error) {
    console.error(`ERROR \n${error.message}`);
  }
};

This is the fetchJoke() function, which (as we can tell by the function name), returns a Joke object. You'll notice a keyword async written before the arrow function begins. This indicates that the function will return a JavaScript Promise. Promises are the subject of a whole other TinyApp, so I won't get into the dense particulars of it.

Rather, understand that a Promise communicates that some return value is on its way from this function, it's promised to arrive. It just needs a bit of time to resolve. To resolve a promise, we have to await its response. This tells JavaScript to wait on the resolution of a promise before proceeding.

We then open up a try...catch block. This is how JavaScript practices error handling. If there are any errors that happen within our try block, they will be caught and the code within the catch block will be run. Since communicating with an API is happening externally, and there can be a variety of things that can mess up, using a try and catch helps our errors to be clearer.

When we enter the try block, we see a response variable that contains the value of the fetch request.

const response = await fetch("https://icanhazdadjoke.com/", {
      headers: {
        Accept: "application/json"
      }
    });

Fetch is an asynchronous function, so we need to await its response in order for it to resolve. It takes a few different arguments (check out the further reading section to see more details). The most important one is the first, the url that data is being fetched from.

If we read the docs for the ICanHazDadJoke API, it specifies that we need to fetch from https://icanhazdadjoke.com/ and that we need to accept a header in 'application/json' if we're trying to parse the data as JSON. When I was writing this TinyApp, I missed that in the documentation, resulting in many vague errors! After studying the errors and revisiting the page, I realized I had forgotten to include headers!

The headers object in the fetch therefore sets the Accept value to application/json. When we run this code, we'll get a response object in JSON format from the Dad Joke API.

The next if block in our code could look a little over the top, but it's important for catching errors that occur in our try block.

  if (!response.ok) {
      const message = `Error: ${response.status}, ${response.statusText}`;
      const error = new Error(message);
      throw error;
    }

If the response isn't ok !response.ok - (which means that the response status from the API isn't a 200) - we throw an error with an error message, containing the status of the error and the statusText. Adding this piece gives more description about what went wrong and will speed up our time to fix it.

If that all goes well and there are no errors, we get to translate our response in JSON to JavaScript. That way, we can actually share the joke on the screen.

const joke = await response.json();
return joke;

We set a variable joke equal to our response.json() which magically parses JSON into JavaScript. Since .json() is also asynchronous, we will have to await it to resolve as well. Once it's resolved, we return the joke variable (a joke object).

Displaying our Joke

Whew! We finally have our joke object ready to display on the screen.

First, let's get some references to the HTML where we want to display the joke, and the button that will trigger us to request a new joke from the API.

let jokePara = document.querySelector("#joke");
let jokeButton = document.querySelector("#jokeButton");

If you remember from earlier in the article, we had a paragraph tag with an id of "joke" and button tag with an id of "jokeButton". These two variables refer to those HTML elements, which we can now manipulate.

To do that, we need to create a function which changes the paragraph tag's contents to the joke that we receive from the API. There are a couple new keywords here, so let's dig in.

const renderJoke = () => {
  fetchJoke().then((jokeObject) => {
    jokePara.innerHTML = jokeObject.joke;
  });
};

While await is certainly one way to let Promises resolve, .then() does the same. While await and .then() aren't exactly the same, they similarly wait for a Promise to resolve before proceeding. In this function, renderJoke(), we first call fetchJoke(). It returns a joke object that's readable in JavaScript, but the function is itself asynchronous. This makes the .then() pattern very useful here.

First, we wait for fetchJoke() to return a JokeObject. Then, with reference to that object, we set the innerHTML of our paragraph tag jokePara to the joke value.

It's time for the final piece. We need to trigger this renderJoke() function when the button on the UI is clicked. That means we need to listen for a click. By adding an eventListener to our jokeButton element, we can tell renderJoke() to run on every click.

jokeButton.addEventListener("click", renderJoke);

We did it! Laugh your merry way as you connect APIs to your JavaScript projects in the future!

Further Reading

On Creating a REST API On Promises ICanHazDadJoke Fetch

ย