Hands on

Building an Alexa Skill with JavaScript and HERE Location Services

By Nic Raboy | 22 November 2018

Having a voice assistant compatible application is a necessity of a lot of modern organizations. With Amazon Alexa powered devices in millions of homes, it makes sense to support these devices. Many applications require location features of some sort whether that be tracking, positioning, or something else. Did you know that you could include location services in your Alexa Skills with HERE?

About a week ago I had written a tutorial titled, Geocode Addresses with Amazon Alexa, Golang, and the HERE Geocoder API which focused on Golang, but what if we wanted to explore another common technology like JavaScript?

We’re going to see how to build an Amazon Alexa Skill using Node.js and simple JavaScript while including HERE Location Services (HLS).

Creating a New Node.js Project with the Dependencies

The core of this application will be JavaScript with Node.js and it will be hosted on AWS Lambda as a Functions as a Service (FaaS) application. The first step for success is to create a new project.

From the command line, execute the following:

npm init -y
npm install ask-sdk --save
npm install request --save
npm install request-promise --save

The above commands will create a new package.json file and install three project dependencies. The first dependency, ask-sdk is the Alexa Skills Kit SDK, and it will allow us to accept requests and return properly formatted Alexa responses. Because we’ll be using HERE, we need a way to make HTTP requests, so the request and request-promise packages will do the job.

The last thing to do to get us started is to create a file that will hold all of our code. Create a main.js file within your project with the following code:

const Alexa = require("ask-sdk-core");
const Request = require("request-promise");

const appId = "APP-ID-HERE";
const appCode = "APP-CODE-HERE";

var skill;

exports.handler = async (event, context) => { };

The above code imports our packages and creates our single handler function that will be called by AWS Lambda. We’re also laying the groundwork when it comes to our variables. Because we’ll be using the HLS platform, we need to have an application id and an application code. Both can be created for free from within the HERE Developer Portal.

Developing an AWS Lambda Function with Multiple Intent Support

Now that we have the basics in place, we can start developing our Amazon Alexa intents. Using the ASK SDK, our requests will be dispatched between multiple intents, each executing their own logic, all while using the same AWS Lambda function.

Let’s look at a basic intent:

const AboutHandler = {
    canHandle(input) {
        return input.requestEnvelope.request.type === "IntentRequest" && input.requestEnvelope.request.intent.name === "AboutIntent";
    },
    handle(input) {
        return input.responseBuilder
            .speak("Geocoder was created by Nic Raboy in Tracy, California")
            .withSimpleCard("About Geocoder", "Geocoder was created by Nic Raboy in Tracy, California")
            .getResponse();
    }
}

The above AboutHandler object contains two functions. The first function checks to make sure that the request was for the AboutIntent and that it is in fact an IntentRequest. The second function executes logic assuming the canHandle function succeeded. We can build a response that contains things like what Alexa should speak or what should appear in the cards found in the mobile application.

To have a well crafted Skill, we should also have an error handler:

const ErrorHandler = {
    canHandle(input) {
        return true;
    },
    handle(input) {
        return input.responseBuilder
            .speak("Sorry, I couldn't understand what you asked. Please try again.")
            .reprompt("Sorry, I couldn't understand what you asked. Please try again.")
            .getResponse();
    }
}

The above object has the same configuration as the previous AboutIntent, but this time the canHandle function always returns true. If any intents return an error, we’ll be configuring the ErrorHandler to take over.

So let’s wire together what we have already.

Remember, this is an AWS Lambda function so there will be a single point of entry. The single point of entry will be the handler function that we exported at the very beginning. We want it to look like the following:

exports.handler = async (event, context) => {
    if(!skill) {
        skill = Alexa.SkillBuilders.custom()
            .addRequestHandlers(
                AboutHandler
            )
            .addErrorHandlers(ErrorHandler)
            .create();
    }
    var response = await skill.invoke(event, context);
    return response;
};

Because the Lambda instance can already be active, we check to see if the skill variable is defined. If it is, the function is still active and we don’t need to recreate it. Otherwise, we add our handlers, define the error handler, and invoke what we have. For every intent we wish to create, it will be added to the .addRequestHandlers method.

Now that we have a basic Alexa Skill, we need to add location services with HERE. For this example, we’re going to give Alexa an address and get a latitude and longitude coordinate back. Before we build the handler object, we need to construct our request to the HERE REST API.

Take the following getPosition function:

const getPosition = async (query) => {
    try {
        var result = await Request({
            uri: "https://geocoder.cit.api.here.com/6.2/geocode.json",
            qs: {
                "app_id": appId,
                "app_code": appCode,
                "searchtext": query
            },
            json: true
        });
        if(result.Response.View.length > 0 && result.Response.View[0].Result.length > 0) {
            return result.Response.View[0].Result[0].Location.DisplayPosition;
        } else {
            throw "No results were returned";
        }
    } catch (error) {
        throw error;
    }
}

In the above function we make a request to the API, providing the application id, the application code, and a query such as an address or similar. As a result, we’re going to return the DisplayPosition which contains the latitude and longitude position.

From an intent perspective, we can build the following:

const GeocodeHandler = {
    canHandle(input) {
        return input.requestEnvelope.request.type === "IntentRequest" && input.requestEnvelope.request.intent.name === "GeocodeIntent";
    },
    async handle(input) {
        try {
            var position = await getPosition(input.requestEnvelope.request.intent.slots.address.value);
            return input.responseBuilder
                .speak(position.Latitude + ", " + position.Longitude)
                .withSimpleCard("Address Position", position.Latitude + ", " + position.Longitude)
                .getResponse();
        } catch (error) {
            throw error;
        }
    }
}

If the GeocodeIntent is requested, we can make a request to the getPosition function. The query is provided as slot values from the Alexa request. These slot values will be defined in the deployment phase.

Building the response follows the same procedure as the previous intent.

Packaging, Testing, and Deploying the Lambda Function as an Amazon Alexa Skill

As of right now, we have a functional AWS Lambda function that uses the Alexa Skills Kit. As you’ve probably noticed, it is more or less just a few functions wrapped in a framework. The Skill could be more or less complex. We don’t have an Alexa Skill until we complete the process.

The first part of the process is to package the function so it can be deployed. Execute the following command:

zip -r handler.zip ./node_modules/* ./main.js

If you don’t have the zip command in your Terminal, just ZIP the node_modules directory along with the main.js file. Had we been using native dependencies for Node.js, we would have had to take further steps. If you come to a scenario where native dependencies are required, take a look at the tutorial I wrote titled, Deploying Native Node.js Dependencies on AWS Lambda. Native dependencies are those that use C libraries that must be compiled on a per architecture and operating system basis.

When deploying to AWS Lambda, make sure to choose Node.js as the function type and give it the Alexa Skills Kit as the trigger. You can then upload the ZIP archive to that particular trigger. Take note of the ARN value for the function as it will be used when defining the Skill.

Head over to the Alexa Developer Portal and create a new Skill. During the process, you’ll be prompted with a checklist of things that must be completed, starting with the invocation name. Make sure the invocation name is something Alexa will be able to understand. This means don’t start making up words.

After defining the invocation name, you need to define your intents and the sample utterances that power them. We should have two intents, the AboutIntent and the GeocodeIntent. For sample utterances, the AboutIntent should have something like the following:

give me information
who made this skill
who made this application

The above are three sample utterances. In reality, you’re going to want to create every possible phrase for your intent. The more you have, the better the user experience. So for the GeocodeIntent you might have the following:

give me the position for {address}
find the position for {address}
what are the coordinates for {address}

Again, the more utterances you have, the better your Skill. However, notice the address variable in brackets. This is a slot that is used as query data for our getPosition function.

Finish through the checklist and proceed to testing your Skill. Assuming your Skill runs appropriately in the simulator, it can be published.

Conclusion

You just saw how to build an Amazon Alexa Skill using Node.js that included the HERE Geocoder API for converting verbally spoken addresses to their latitude and longitude coordinates. Most of this tutorial was around the actual setup and deployment to AWS Lambda which is good, because using HERE Location Services should not be complicated or time consuming.

If you’re interested in accomplishing the same with Golang, check out my previous tutorial titled, Geocode Addresses with Amazon Alexa, Golang, and the HERE Geocoder API.