How do I create an Alexa Skill that gets data from an HTTP/HTTPS API (using "Alexa Skills Kit" for Node.js on AWS Lambda)

Dirk Paessler picture Dirk Paessler · Feb 23, 2017 · Viewed 13.1k times · Source

I want to create a skill for Amazon Alexa that - when triggered by voice commands - gets some information from an API via a HTTPS request and uses the result as spoken answer to the Alexa user.

There is a little challenge here (especially for node.js newbies) due to the event-driven concept of node.js and the internals of the Alexa Skills Kit for Node.js. And getting a hand on parameters from the user isn't that easy, either.

Can somebody provide some sample code to start with?

Answer

Dirk Paessler picture Dirk Paessler · Feb 23, 2017

Preliminaries

  • To get started you need an Amazon account, and you must enable AWS for the account.
  • Then there is a nice step-by-step guide on the Amazon Website: https://developer.amazon.com/edw/home.html#/skills
  • It walks you through step-by-step through the process of creating a "skill". A skill is the ability for Alexa to answer questions in natural language. During this process you also create a Lambda function (select to create one of the demo script applications, and you get all required libraries automatically)
  • Then you can then edit the code in the WebUI of the AWS Console).
  • The "skill" is automatically enabled on all your personal Alexa Devices, like my Amazon Echo dot at home.
  • remember that you can look at the console output in your AWS Cloudwatch section of the AWS console.

The two things I had to understand (and that others may run into, too)

While I created my first Alexa Skill I was new node.js, Lambda and the Alexa Skills SDK. So I ran into a few problems. I'd like to document the solutions here for the next person who runs into the same problem.

  1. When you make an http get request in node.js using https.get() you need to provide a handler for the end callback like res.on('end', function(res) {});
  2. The answer is sent back from the Lambda script to the Alexa Service when you call this.emit(':tell', 'blabla'); (this is what is used in the samples from AWS). But in the end-handler "this" isn't the right "this" anymore, you need to store the handle beforehand (I am doing this a little crookedly using mythis, I am sure there are smarter solutions, but it works).

I would have easily saved two hours of debugging had I had the following code snippet. :-)

The code

I my sample the lambda script already gets the preformatted text from the API. If you call an XML/JSON or whatever API you need to work with the answer a bit more.

'use strict';

const Alexa = require('alexa-sdk');
var https = require('https');

const APP_ID = ''; // TODO replace with your app ID (OPTIONAL).

const handlers = {

  'functionwithoutdata': function() {
    var responseString = '';
    var mythis = this;
    https.get('**YOURURL**?**yourparameters**', (res) => {
      console.log('statusCode:', res.statusCode);
      console.log('headers:', res.headers);

      res.on('data', (d) => {
        responseString += d;
      });

      res.on('end', function(res) {
        const speechOutput = responseString;
        console.log('==> Answering: ', speechOutput);
        mythis.emit(':tell', 'The answer is'+speechOutput);
      });
    }).on('error', (e) => {
      console.error(e);
    });
  },

  'functionwithdata': function() {
    var mydata = this.event.request.intent.slots.mydata.value;
    console.log('mydata:', mydata);
    var responseString = '';
    var mythis = this;
    https.get('**YOURURL**?**yourparameters**&mydata=' + mydata, (res) => {
      console.log('statusCode:', res.statusCode);
      console.log('headers:', res.headers);

      res.on('data', (d) => {
        responseString += d;
      });

      res.on('end', function(res) {
        const speechOutput = responseString;
        console.log('==> Answering: ', speechOutput);
        mythis.emit(':tell', 'The answer is'+speechOutput);
      });
    }).on('error', (e) => {
      console.error(e);
    });
  }

};

exports.handler = (event, context) => {
  const alexa = Alexa.handler(event, context);
  alexa.APP_ID = APP_ID;
  alexa.registerHandlers(handlers);
  alexa.execute();
};