Alexa Skill Recipe: Making HTTP Requests to Get Data from an External API

Amit Jotwani Apr 04, 2018
Share:
Tips & Tools Build Node.js
Blog_Header_Post_Img

Creating an Alexa skill is like cooking a delicious meal. There are many recipes to choose from and several ingredients that go into each one. The Alexa Skill-Building Cookbook on GitHub gives you the recipes and ingredients to build engaging Alexa skills, using short code samples and guidance for adding features to your voice experience. With each installment of the Alexa skill recipe series, we’ll introduce you to a new recipe that can help you improve your voice design and skill engagement. You’re the chef. Let’s get cooking!

As a developer, every day you interact with remote APIs and web servers. Almost all services we consume on the internet today are available in the form of an API: weather forecasts, geolocation services, Twitter feeds, and so on. When creating a skill, you are sure to run into situations in which you would want to get meaningful data from one of these remote sources by making HTTP requests to their APIs. HTTP requests allow you to fetch data from a remote source. It could be an API, a website, or something else.

Making HTTP requests is a core functionality for modern languages, and when it comes to Node.js there are a fair amount of solutions to this problem, both built into the language and by the developer community. In this skill recipe, we look at two of the most popular methods you can use to call an external API through your Alexa skill using Node.js:

  • Recipe 1: Using the built-in "HTTP" or "HTTPS" modules in the standard library
  • Recipe 2: Using the "Request" module

We’ll look at both of these recipes to cook up some API integration into our skill. We will also look at the pros and cons of each approach.

Adding an HTTP Request to a Fact Skill

First, we’ll take this simple fact skill (written in Node.js), and modify the use case slightly to get facts about numbers (instead of space facts). Instead of using the hard-coded data as shown below, we will get the facts in real time by calling the simple (yet awesome) numbersapi.com, which is an API that offers interesting facts about numbers. You can give it a try in the browser by typing: https://numbersapi.com/21.

Here’s the kind of conversation we want to serve with this skill:

User: Alexa, ask number facts to give me a fact about 7 <Triggers the intent GetNumberFactIntent, and passes the number 7 as an AMAZON.NUMBER slot type>

Alexa: 7 is the number of main stars in the constellations of the Big Dipper and Orion. <Our skill calls numbersapi.com with https://numbersapi.com/7, and responds back with the fact>

This is what our code looks like with facts hard-coded into the skill:

Existing hard-coded data with facts about few numbers:

Copied to clipboard
const numberFacts = {
    "1":"is the number of moons orbitting the earth.",
    "2":"is the number of stars in a binary star system (a stellar system consisting of two stars orbiting around their center of mass).",
    "3":"is the number of consecutive successful attempts in a hat trick in sports.",
    "4":"is the number of movements in a symphony.",
    "5":"is the number of basic tastes (sweet, salty, sour, bitter, and umami).",
    "6":"is the number of fundamental flight instruments lumped together on a cockpit display.",
    "7":"7 is the number of main stars in the constellations of the Big Dipper and Orion.",
    "8":"is the number of bits in a byte.",
    "9":"is the number of innings in a regulation, non-tied game of baseball.",
    "10":"is the number of hydrogen atoms in butane, a hydrocarbon.",
    "11": "is the number of players in a football team."
}

Your intent handler "GetNumberFactIntent” would then call upon the dataset above when triggered by the user:

Copied to clipboard
    'GetNumberFactIntent': function () { 
        const theNumber = this.event.request.intent.slots.number.value; 
        const theFact = numberFacts[theNumber];                                
        const speechOutput = theNumber + theFact; 
        this.response.cardRenderer(SKILL_NAME, theFact); 
        this.response.speak(speechOutput + " Would you like another fact?").listen("Would you like another fact?"); 
        this.emit(':responseReady'); 
    } 

Let’s now look at the Alexa recipes we can use to call our API to get facts about numbers dynamically.

Recipe 1: Using the Built-In “HTTP” Module in the Standard Library

Let’s begin by taking a quick look at our existing code for the GetNumberFactIntent above that gets fired when the user asks for a fact about a number.

Copied to clipboard
'GetNumberFactIntent': function () { 
    const theNumber = this.event.request.intent.slots.number.value; //line 1
    const theFact = numberFacts[theNumber]; //line 2                                
    const speechOutput = theNumber + theFact; //line 3
    this.response.cardRenderer(SKILL_NAME, theFact); //line 4
    this.response.speak(speechOutput + " Would you like another fact?").listen("Would you like another fact?"); //line 5
    this.emit(':responseReady'); //line 6
    } 

As you can see on line 1 and 2 in the code for our GetNumberfactIntent handler above, we are passing the value of the number we received through our slot to the numberFacts object we looked at earlier to get our fact. Let’s now replace this line with our helper function, which will call numbersapi.com and get the fact about the number on the fly.

Step 1: Include the http module in our skill using the require statement.

Since numbersapi.com supports only HTTP calls, we are including only the HTTP library. Most APIs, however will support HTTPs, in which case you should include ‘HTTPS’ as well (require(‘https’).

Copied to clipboard
var http = require('http’); 

Step 2: Include this helper function in your skill code to make HTTP calls to REST APIs.

Copied to clipboard
function httpGet(query, callback) {
    var options = {
        host: 'numbersapi.com',
        path: '/' + encodeURIComponent(query),
        method: 'GET',
    };

    var req = http.request(options, res => {
        res.setEncoding('utf8');
        var responseString = "";
        
        //accept incoming data asynchronously
        res.on('data', chunk => {
            responseString = responseString + chunk;
        });
        
        //return the data when streaming is complete
        res.on('end', () => {
            console.log(responseString);
            callback(responseString);
        });

    });
    req.end();
}

Let’s quickly run though what’s happening in this code: First, we prepare the options to send on to the HTTP call – we set up the host URI, the path, which is built using the number we receive from the user (query). So, if the user says “give me a fact about 21,” the number 21 is captured through our number slot (AMAZON.NUMBER slot type), and is passed on to the httpGet function wrapped inside the variable query. This code will make an HTTP GET request to the URI numbersapi.com/21. You can try this URL in your browser to see the result: http://numbersapi.com/42.

Then, we need to set up the HTTP request by calling the http.request method, and passing it the options we just set up. Since we are using Node.js, all of the functionality is handled asynchronously, which means we need to set up handlers within our httpGet function to accept incoming data, as well as to deal with the response once it's complete. In this case, incoming data is simple. We just have to accept the data and concatenate it onto a string (responseString). Once the data return is complete, we call our callback function with this response.

Step 3: Finally, call the helper function from within your intent.

Copied to clipboard
    'GetNumberFactIntent': function () {
        const theNumber = this.event.request.intent.slots.number.value;
        var query = parseInt(theNumber);
        
        httpGet(query,  (theResult) => {
                console.log("sent     : " + query);
                console.log("received : " + theResult);
                const theFact = theResult;
                                
                const speechOutput = theFact;
                this.response.cardRenderer(SKILL_NAME, theFact);
                this.response.speak(speechOutput + " Would you like another fact?").listen("Would you like another fact?");
                this.emit(':responseReady');
            });
    }

We call our httpGet helper function from our intent handler (GetNumberFactIntent) by passing it the number we received from the user (query). Once, we receive the result, we use some console.log statements to log the response to CloudWatch, in case we need it to debug our code, and then finally we generate the speech response for the user through this.response.speak.

Pros and Cons of Using “HTTP” Module to Call an API from Your Alexa Skill

The main advantage of using the HTTP module is that there is no need to install external dependencies. It’s a built-in module part of the standard library, so you can just plug and play. If you are using AWS Lambda to host your skill code, this also means that you can make changes to your code by using the inline editor provided by the AWS Lambda console, without needing to upload the dependencies as a zip file to your Lambda function, which certainly comes pretty handy if you don’t like working with the command line.

While the HTTP module can do everything, the downside is that it can feel a bit clumsy to use. Much of the HTTP, and the HTTPS, module’s functionality is fairly low-level. You’re required to receive response data in chunks rather than just providing a callback function to be executed as soon as all of the data is received. You also need to parse the response data manually. This is fairly easy if the data returned by the API is in the JSON format, but it is still an extra step.

Recipe 2: Using the “Request” Module

The Request module is one of the most popular NPM module for making HTTP requests. It supports both HTTP and HTTPS and follows redirects by default. Behind the scenes, it actually uses the http module and adds a bit of magic to make it easier to use. A lot of common cases can be handled with just a tiny bit of code. The downside of using the “Request” module is that it’s not something that’s built-in to the standard library, and needs to be installed using npm, the default package manager for Node.js.

Having said that, as you expand the functionality of your skill, you will find yourself the need to use even more external modules, available through npm, like the `aws-sdk` module that makes it easy to work with the AWS services like Lambda, S3, Amazon DynamoDB, and more. This means that eventually you’d graduate to using this approach one way or the other. So, this isn’t really a disadvantage in the long run.

Let’s see how we can modify the code to use the “Request” module to call our API.

Step 1: Include the request module in our skill using the require statement.

Copied to clipboard
    const request = require('request');

Step 2: Call the request.get() method from within your intent (GetNumberFactIntent) to send a query (number requested by the user) to the API, and respond back with the result.

Copied to clipboard
    'GetNumberFactIntent': function () {
        const theNumber = this.event.request.intent.slots.number.value;
        var myRequest = parseInt(theNumber);
        const url = `http://numbersapi.com/${theNumber}`;
        
        request.get(url, (error, response, body) => {
            // let json = JSON.parse(body);
            console.log('error:', error); // Print the error if one occurred
            console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
            console.log('body:', body); // Print the body

            const theFact = body;                  
            const speechOutput = theFact;
            this.response.cardRenderer(SKILL_NAME, theFact);
            this.response.speak(speechOutput + " Would you like another fact?").listen("Would you like another fact?");
            this.emit(':responseReady');
        });
    }

As you can see, the “request” module makes it super simple for us to call our API. All we had to do was specify the URL for the API to be called, and the response is made available to us inside the object body, which we can then parse and use as part of our response.

The next step after this would be to zip up your index.js file along with other modules to create a deployment package and upload it to your Lambda function. To learn more about how to do that, read Prepare a Node.js Sample to Deploy in Lambda.

Choosing Your Recipe

You can obviously choose to use either of the recipes depending on your use case. A good rule of thumb to consider is that if your skill is using other external modules already, it may make sense to use the “requests” module and make your code simpler to read and easier to maintain. If, however, your skill is not using any other external modules and you don’t mind the extra bit of complexity in your code, you can stick with the “HTTP” module approach.

For more recipes, visit the Alexa Skill-Building Cookbook on GitHub.

More Resources

Build Engaging Skills, Earn Money with Alexa Developer Rewards

Every month, developers can earn money for eligible skills that drive some of the highest customer engagement. Developers can increase their level of skill engagement and potentially earn more by improving their skill, building more skills, and making their skills available in in the US, UK and Germany. Learn more about our rewards program and start building today. Download our guide or watch our on-demand webinar for tips to build engaging skills.