Editor's Note (9/10/2019): The content in this post is outdated. A new technical walkthrough of local debugging can be found in "Setup Your Local Environment for Debugging an Alexa Skill."
Amazon Alexa and the Alexa Skills Kit (ASK) are enabling developers to create voice-first interactions for applications and services. In this article, we will cover how to set up a local development environment using the Amazon Web Services (AWS) SDK for NodeJs.
By following this tutorial, you’ll be able to invoke your AWS Lambda code as if were called by the Alexa service. This will also allow you to interact with any other AWS services you may have added to your skill logic such as Amazon DynamoDB. By the end of this post, you will be able to execute and debug all of your Alexa skill’s Lambda code from your local development environment.
Using the aws-sdk, you should also be able to call any dependent services in AWS as if the skill code were executing in AWS Lambda by leveraging AWS Roles. This way, you can be sure your code is working before deploying into AWS and hopefully decrease the cycle time for applying new changes. For example, you want to persist something about users in a DynamoDB table and the only way to do this was run your code in Lambda. After this tutorial, you should be able to write to the remote Dynamo table from your local environment.
First, let’s take a look at why you would want to streamline this process. The first time I developed a skill, I was not using an integrated development environment and almost all debugging information was obtained through log statements. This presents quite a few challenges from a developer’s point of view.
I wanted a better way to execute and debug my code, but not lose any of the functionality of being constrained to a local environment.
In the next section we will look at how to setup a local environment to debug your AWS Lambda code using Node,js, Microsoft's Visual Studio code open-source editor, and the aws-sdk npm package. This tutorial will cover setting this up using Node.js but the AWS SDK is available for Python and Java as well.
Install Node.js via the available installer. The installation is fast and easy, just follow the available prompts. For the purposes of this tutorial, I am on OSX, so I selected v4.5.0 LTS. There are versions available for Windows and Linux as well.
Repeat the process with Microsoft's Visual Studio Code. For the purposes of this tutorial, I am using Microsoft’s Visual Studio Code but others should work as well.
Follow the instructions in this guide to get the AWS cli set up on your environment.
That's all you will need to get started. Next we will load our Alexa project and setup the IDE to debug the Lambda code.
For the purposes of this tutorial, we are going to use the High-low guessing game template from the public GitHub repo for Alexa.
4. Once opened, expand the file tree on the left and click src/index.js and you should see the following view:
5. Install the required npm module name alexa-sdk from line 2. From the menu, select View->Integrated Terminal
6. You should see a command line tool like the following:
7. Enter the following commands:
cd src npm install –-save alexa-sdk
8. After installing the module, you should see the following output in your terminal:
and a new directory in your project called src/node_modules.
Note how we installed alexa-sdk package in the source directory for our skill code. This is important as to keep test files and modules out of the zip file uploaded to AWS Lambda. Currently AWS Lambda does not have the alexa-sdk module built in, you will need to upload it as part of your Lambda function source code.
Now let’s add a few things to get ready to debug. We’ll start by creating a test directory in the root folder of your project and add the following three files to the project:
To add a folder, hover over the root of the tree view in the left pane and click the New Folder button. Note, if you had the src and speechAssets directories expanded, make sure to collapse the tree view and click the root directory of the project. It should look like the image below. Just make sure the test directory is under the root of the project and not a child of the src folder. If you are on a Windows machine, you’ll need to select the LICENSE.txt file and then click the icon to create the folder.
Name the directory test and the resulting view should look like the following.
Next, add three empty files to your project under the test directory. Click the test directory and then hover over the root node to find the New File icon
Repeat this three times and add the following files:
Now, let’s add some code to these new files. You can ignore some of the values defined in these files for now. We will configure them for your project later in this tutorial.
You can also download the code for all three files
// Copyright 2015, Amazon Web Services. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // the role ARN to assume for any AWS SDK related calls // the role must have a trusted policy with // "lambda.amazonaws.com" and "arn:aws:iam::<YOUR ACCOUNT ID>:user/<YOUR USER>" var roleArn = 'arn:aws:iam::<YOUR AWS ACCOUNT ID>:role/lambda_dynamo'; var region = 'us-east-1'; /* DO NOT MAKE CHANGE BELOW THIS */ var AWS = require('aws-sdk'); function context() { var context = require('./context.json'); context.done = function(error, result) { console.log('context.done'); console.log(error); console.log(result); process.exit(); } context.succeed = function(result) { console.log('context.succeed'); console.log(result); process.exit(); } context.fail = function(error) { console.log('context.fail'); console.log(error); process.exit(); } return context; } AWS.config.region = region; var sts = new AWS.STS(); sts.assumeRole({ RoleArn: roleArn, RoleSessionName: 'emulambda' }, function(err, data) { if (err) { // an error occurred console.log('Cannot assume role'); console.log(err, err.stack); } else { // successful response AWS.config.update({ accessKeyId: data.Credentials.AccessKeyId, secretAccessKey: data.Credentials.SecretAccessKey, sessionToken: data.Credentials.sessionToken }); var Module = require('module'); var originalRequire = Module.prototype.require; Module.prototype.require = function(){ if (arguments[0] === 'aws-sdk'){ return AWS; } else { return originalRequire.apply(this, arguments); } }; var lambda = require('../src/index.js'); var event = require('./input.json'); lambda.handler(event, context()); } });
{ "clientContext" : { "env" : { } } }
{ "session": { "sessionId": "SessionId.Session1", "application": { "applicationId": "amzn1.echo-sdk-ams.app.<your skill id here>" }, "user": { "userId": "amzn1.ask.account.testaccount1" }, "new": true }, "request": { "type": "IntentRequest", "requestId": "EdwRequestId.TestSession1", "timestamp": "2016-05-19T01:07:55Z", "intent": { "name": "LaunchIntent" }, "locale": "en-US" }, "version": "1.0" }
Notice line 21 of test/main.js that says
var AWS = require('aws-sdk');
that means the test code depends on this module at runtime, so we need to install it locally. Repeat the process above, using the internal terminal, and install the npm module. Ensure you are in the test directory of your project
Next we will need to configure permissions on your local environment to assume the role used in the Alexa service.
In a normal invocation of your skill:
1. A user speaks to their Alexa device or emulator (e.g. echosim.io)
2. The speech is sent to the Alexa service and routed to the correct Lambda/Intent
Note: The Alexa service has specific permissions to call this service.
3. The Lambda returns speech text in a JSON response along with an optional card to the Alexa app.
4. The Alexa service routes the responses and speaks the response to the user.
When you create a Lambda function in AWS you define an execution role for the function. An execution role in this context is the role your function will inherit when invoked. The process to mimic this role as if your local Node.js code were executing in AWS is:
If you haven’t created a role used by your skill or haven’t created the Lambda function leveraging that role, please see the guide on the Alexa GitHub page. For the purposes of this tutorial, I created a role called lambda_dynamo that has the CloudWatchFullAccess and AmazonDynamoDBFullAccess policies attached. Since this is the role the Lambda function will inherit when invoked, these two policies ensure that the Lambda function can write to the cloudwatch logs and read/write to DynamoDB.
This is the role I defined in my Lambda function for its execution role.
Let’s step through the process of creating the new IAM user and adding the right policies.
To start, go to your AWS console and if you don't have a user created already, let’s create one specifically for debugging your Lambda function.
From the AWS console, from the menu bar, select Services->IAM:
Next click the Users link in the left sidebar:
Click the Create New Users button:
Enter a User Name on the next screen, for this tutorial I chose debugUser. Leave the "Create an access key for each user" checkbox enabled, and click Create:
Return to the list of users and click the one you just created to see the details:
Copy the User ARN field, you're going to need it in a minute.
The Assume Role policy in AWS IAM gives a user the ability to inherit the execution role of your Lambda function. We will need to add this policy to the user you just created.
Click the Roles menu item on the left menu and select the lambda_dynamo role used for this example if you created the same or select the role your Lambda function is using.
Open the role by clicking on the name:
Next click the Trust Relationships tab and then the Edit Trust Relationship button:
Copy the ARN value for the user your created earlier into the trust relationship
"AWS":"<the user arn value copied from the previous step">
to the Principal object, right above the value for Service. After adding, the file should look something like this:
Click the Update Trust Policy button.
Notice, after defining the trust relationship the debugUser is listed as a Trusted Entity. On the same detail screen, copy the value for Role ARN
into the line 22 in test/main.js
var roleArn = '<the role ARN string>’;
Now we need to take the keys you saved when creating the user and configure your local AWS CLI. Open up a terminal and type:
aws configure
You will be prompted for the values for:
In my example below, I already had the user configured so the default values were preset with <enter>. If this is the first time configuring the user, copy and paste the values captured when you created your IAM user and saved the AWS Access Key ID and AWS Secret Access Key.
In the sample code provided above, the file for input.json contains placeholder values for
NOTE: If you have not created the Alexa skill in your developer account or the Lambda function in your AWS account, stop here and follow this guide for creating your first skill. Optionally, if you want to perform this tutorial without creating a skill, you will need to match the value for session.application.applicationId to line 3 of src/index.js:
Let’s take a look at how you modify those for your skill.
You need to update the file with the application Id for your Alexa skill. To get this value, login to the developer portal, from the Alexa tab, select Alexa Skills Kit and click the Get Started button:
Click your skill to bring up the detail page, and select the value listed for Application Id:
Paste this into line 5 of test/input.json
This value does not need to be a special value and can be created on the fly, but know that this is how Alexa identifies accounts enabled on the Alexa device. If you are storing values specific to a user, you will want to change this value in different scenarios to simulate different users.
For more information about the request format and the userId value, you can visit our ASK reference page.
The most basic case for your skills should be the LaunchIntent. This will test whether your skill code is being invoked correctly. In the sample the value for request.intent.name is LaunchIntent and is by default mapped to the launch intent handler of your Alexa skill.
You can leave this alone to test basic functionality, but you are probably going to want to test real intents with real values. Let's look and see how you would do that.
Notice that in the highlow-game template we are using in this example has an intent named NumberGuessIntent that expects a slot value named number. The datatype for that is one of Alexa’s built-in types AMAZON.NUMBER
To simulate this event, edit test/input.json and replace line 17 so the request object matches the defined intent:
{ "session": { "sessionId": "SessionId.Session1", "application": { "applicationId": "amzn1.echo-sdk-ams.app.<your skill id here>" }, "user": { "userId": "amzn1.ask.account.testaccount1" }, "new": true }, "request": { "type": "IntentRequest", "requestId": "EdwRequestId.TestSession1", "timestamp": "2016-05-19T01:07:55Z", "intent": { "name": "NumberGuessIntent", "slots": { "number": { "name": "number", "value": "100" } } }, "locale": "en-US" }, "version": "1.0" }
Different content for input.json means the ability to programmatically test each intent hander in your code. But manipulating this content every time you want to test a new function would be tedious. It would be nice to be able to regression test these by running through a suite of input.json files. The developer console has some information on using the built-in test feature of the skill console, but let’s take a closer look and see how it can be used to our advantage.
Find the Test tab of the developer console for your skill:
Use the Service Simulator and type in the utterance you would say if speaking into an Alexa-enabled device. For example, if we take a look at the utterances defined for the highlow-game template, we see the following for the NumberGuessIntent
This means some of the example phrases we can use to invoke this intent that would match the previous example (notice how the numbers are spelled out). For the purposes of this tutorial, let’s assume you assigned an invocation name of number guesser. The full phrase a user might say to an Alexa device would be “Alexa, ask number guesser is it one hundred.
The Service Simulator does not require the wake word (Alexa) or the invocation words (ask number guesser) to simulate the invocation. Let’s enter the words “is it one hundred” and see what happens.
NOTE: This section does not apply if you have not created the skill in the developer console. Access to the Service Simulator is through a defined skill.
And look in the first text area called Lambda Request, it should contain a complete json formatted request we can reuse in our local environment. Copy that request into a new file in the test directory of your project and name it something appropriate. In this example, I named it test/guess100.json
To switch between tests, you just need to edit the test/main.js file and change input.json to whatever file you want.
There is a file for creating a context for your Lambda function. For the purposes of this tutorial, we are not passing any meaningful data to the lambda in the context, but it is needed to create the context object in test/main.js.
For more information please see the Context Object in the AWS documentation.
At this point, you should have your environment ready for debugging. Your folder view should look like this:
Open up the skill code you want to debug, in this example src/index.js contains the business logic for the sample skill.
To set a breakpoint, click to the left of the line number in the editor view. In this example, I set a breakpoint on the line 21. When we fire the test event test/guess100.json, this code should be executed.
Click the bug icon in the left menu bar to start a debug session:
If this is the first time you have debugged code in this tool, you will need to select the debugging environment. Choose Node.js from the choices after clicking the gear icon:
You will also need to setup a debug configuration for this skill project, the tool should prompt you and generate a launch.json file:
We only need to make one change to launch and debug at this point.
Change the value for 'program' to match the main.js file in your test dir. Save the file.
{ "version": "0.2.0", "configurations": [ { "name": "Launch", "type": "node", "request": "launch", "program": "${workspaceRoot}/test/main.js", "stopOnEntry": false, "args": [], "cwd": "${workspaceRoot}", "preLaunchTask": null, "runtimeExecutable": null, "runtimeArgs": [ "--nolazy" ], "env": { "NODE_ENV": "development" }, "externalConsole": false, "sourceMaps": false, "outDir": null }, { "name": "Attach", "type": "node", "request": "attach", "port": 5858, "address": "localhost", "restart": false, "sourceMaps": false, "outDir": null, "localRoot": "${workspaceRoot}", "remoteRoot": null } ] }
Start Debugging
Go back to the debug view and click the green triangle:
In the screenshot below, you can see that the debugger stopped on our breakpoint and the correct test/guess100.json file was passed into the intent object from the local variables view.
You can use the normal step-into, step-over, and step-out debugger functions to appropriately dive into your code and the state of any of the objects.
In this tutorial we demonstrated how to setup a local environment for the Lambda code of your Alexa skill written in Node.js, how to create a test user in AWS and assign the Assume Role policy to that user so it can inherit the same execution role defined for a Lambda function, create and store various input files to test the different intents implemented in your code, and set breakpoints in those intents to step through the code. This strategy can be used to interact with other AWS services by applying the same Assume Role relationship to your test user for the various roles used in Alexa and AWS.
For next steps, take a look at Josh Skeen's post on Developing Locally to understand how to use a promise framework to put unit tests and build automation into your project.