8. Node and an API + Axios

So cool! We've got ourselves and idea of how APIs work. It's time to pull that into your app so you can play with it. I've wiped out all the stuff in my example, but you may want to start up a new file, or save your old stuff for later reference.


We're putting quite a few things together. We're first going to use npm to install a package called Axios, so we can interact with the internet. It's pretty popular and I like the way it lays out responses and what not.

What you haven't seen yet, is the formatting you want to use on your "thenable" functions. Functions that support asynchronous then statements make for pretty readable code, and also allow us to catch errors for reporting.

const axios = require('axios').default;

async function main() {
    let r2D2 = await axios
        .get('http://swapi.dev/api/people/3/')
        .then(res => {
            return res.data;
        })
        .catch(e => {
            console.error("Issue pulling data for R2D2: ", e.response.data);
        })

    console.log("R2D2's info: ", r2D2);
}

main();

So check it out! We've got our require statment at the top, that will only work after you've use npm to install axios (as a reminder, it's npm install axios).

Then we've got our main function, and inside that we create a nice new variable for R2.

We have the assignment for that variable await a response from our instance of Axios. We format this in a nice shiny way, and return info for R2 to be seence in the console. If there's an error, we catch that bad boy and show it in console well, specifically as a console error.

Quick not on "Arrow functions". What you're seeing is that there's a response from the function/method we're using in axios, and we're passing that response straight into a standalone function, like so res => {}. This is short hand for doing something icky, like this

function handleAxiosResponses(res) {return res.data;}
function handleAxiosErrors(e) {console.error(e.response.data);}

let r2D2 = await axios
        .get('http://swapi.dev/api/people/3/')
        .then(handleAxiosResponses(res);)
        .catch(handleAxiosErrors(e);)

It's pretty redundant, and you probably won't need to repeat those functions again. Soooooooo arrow function to the rescue!

Let's talk about that "thenable" formatting really quick. Make way for comments!

let r2D2 = await axios // this is our await statement
        .get('http://swapi.dev/api/people/3/') // the method from axios we're using is .get(), which signifies the HTTP method we're using. GET.
        .then(res => { // then, we get the response from that, which is a big JSON object, passed into our arrow function as a variable named res. you can actually name it anything you want before that arrow
            return res.data; // and we have this return all the way back to r2D2, *just* the data that was in that response. there's a bunch of other things too
        })
        .catch(e => { // here is our catch. if there's an error in our API call, it gets passed into this catch statement for us to work with
            console.error("Issue pulling data for R2D2: ", e.response.data);
            return undefined;
        })

This makes for some long code to read, but you'll get super used to it. The response popping into your console should look just like what we saw in the API blog post.

john.celoria@MacBook-Pro-2 hello-world % node .
R2D2's info:  {
  name: 'R2-D2',
  height: '96',
  mass: '32',
  hair_color: 'n/a',
  skin_color: 'white, blue',
  eye_color: 'red',
  birth_year: '33BBY',
  gender: 'n/a',
  homeworld: 'http://swapi.dev/api/planets/8/',
  films: [
    'http://swapi.dev/api/films/1/',
    'http://swapi.dev/api/films/2/',
    'http://swapi.dev/api/films/3/',
    'http://swapi.dev/api/films/4/',
    'http://swapi.dev/api/films/5/',
    'http://swapi.dev/api/films/6/'
  ],
  species: [ 'http://swapi.dev/api/species/2/' ],
  vehicles: [],
  starships: [],
  created: '2014-12-10T15:11:50.376000Z',
  edited: '2014-12-20T21:17:50.311000Z',
  url: 'http://swapi.dev/api/people/3/'
}
john.celoria@MacBook-Pro-2 hello-world % 

Each on of those keys is now accessible! We can work all kinds of stuff with it. Let's mess with that films array, and pull info on each one of those.

const axios = require('axios').default;

async function main() {
    let r2D2 = await axios
        .get('http://swapi.dev/api/people/3/')
        .then(res => {
            return res.data;
        })
        .catch(e => {
            console.error("Issue pulling data for R2D2: ", e.response.data);
        })

    console.log("R2D2's info: ", r2D2);

    for(let film of r2D2.films) {
        console.log("Film api adress: ", film);

        await axios
            .get(film)
            .then(res =>{
                console.log("Film info: ", res.data);
            })
            .catch(e => {
                console.error("Issue pulling info on the film: ", e.response.data)
            })
    }
}

main();

Cool! I won't copy out the information that just showed up in my console, it's kind of long. Notice that I'm not event sending this info back to any variable, I'm just getting the info, and logging it out into my terminal. I'm doing this one at a time, with a for loop, so everything should come out in the same order that you saw in the r2D2 films array.

Not let's play around with synchronous!!

const axios = require('axios').default;

async function main() {
    let r2D2 = await axios
        .get('http://swapi.dev/api/people/3/')
        .then(res => {
            return res.data;
        })
        .catch(e => {
            console.error("Issue pulling data for R2D2: ", e.response.data);
        });

    console.log("R2D2's info: ", r2D2.films);

    console.log("*** ASYNC ***");

    for (let film of r2D2.films) {
        await axios
            .get(film)
            .then(res => {
                console.log("Film info: ", res.data.title);
            })
            .catch(e => {
                console.error("Issue pulling info on the film: ", e.response.data);
            });
    }

    console.log("\n***SYNC***");

    r2D2.films.forEach(film => {
        axios
            .get(film)
            .then(res => {
                console.log("Film info: ", res.data.title);
            })
            .catch(e => {
                console.error("Issue pulling info on the film: ", e.response.data);
            });
    })
}

main();

Alright, I've basically dumped the same axios call into two different loops. One for loop, the other a forEach loop. My console looks like so

john.celoria@MacBook-Pro-2 hello-world % node .
R2D2's info:  [
  'http://swapi.dev/api/films/1/',
  'http://swapi.dev/api/films/2/',
  'http://swapi.dev/api/films/3/',
  'http://swapi.dev/api/films/4/',
  'http://swapi.dev/api/films/5/',
  'http://swapi.dev/api/films/6/'
]
*** ASYNC ***
Film info:  A New Hope
Film info:  The Empire Strikes Back
Film info:  Return of the Jedi
Film info:  The Phantom Menace
Film info:  Attack of the Clones
Film info:  Revenge of the Sith

***SYNC***
Film info:  A New Hope
Film info:  The Empire Strikes Back
Film info:  Return of the Jedi
Film info:  Revenge of the Sith // out of order
Film info:  Attack of the Clones
Film info:  The Phantom Menace // out of order
john.celoria@MacBook-Pro-2 hello-world % 

Cool!!! This is such an important thing to note! When you run this, the order of the movie names could be the exact same for you. In my case, some network lag cause my sync calls to show the names out of order. That's because node is just sending each API call as fast as it can, and when it gets the response back, prints it out. For me, I got the name back for "Revenge of the Sith", for "Attack of the Clones" and "The Phantom Menace" completely putting things out of order!

Now think of something you might want to do with the RC API. Like create an extension, then come back to that extension and change its extension number, or email. That has to happen in order. That's perfect for ASYNC. ASYNC will block the code from continuing on, until we get back a response about the create extension API call. With SYNC, maybe it happenss in the right order?? If we were just updating an email on an array of different extension IDs, then SYNC might be ok for that, since order doesn't matter, just that the update happens without an error.

Now for authorization. Blegh.