This post first appeared on dev.to

Hey all :wave: we are gonna see how to setup an Alexa skill for a trivia related to space events, using the Serverless framework. It’s assumed you are ok with some Alexa fundamentals and certain concepts of AWS such as IAM, Lambda etc. Let’s get started.

You may go through this nice tutorial if you want to get started with Alexa by building an Alexa hosted skill. What we have done here in this blog is an AWS hosted skill.

Alexa

Login to the Alexa skill kit console with your developer account.

You can then create a skill and give it some invocation name. Skill invocation name

Intents

I’ve addedd 2 built-in intents and 2 custom intents, than what gets added by default. Added intents

The GetAnswerIntent has the following utterances.

it's {date}
I guess it's on {date}
I think the date is {date}
{date}
I'm guessing it's {date}

You may add more or modify as you wish. The only slot here is date and it is of built-in type AMAZON.DATE.

Likewise the AskQuestionIntent has the following utterances.

could you ask me the question please
can you go back to the question
can you ask me the question
question please
go back to the question
next question
Ask question

Ensure to save and build the model once all the intents are added.

IAM

Log in to AWS as a root user, and create a user group Create group

The user group should be attached by the AWS managed permission policy AWSCloud9User that allows creating Cloud9 environments. This is not required though, if you are not using Cloud9.

I’ve then created another custom policy with the following JSON, with the name ServerlessPolicy that gives enough permissions for the serverless SDK to deploy using cloud formation stack, and access to other relevant services as listed.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "apigateway:DELETE",
                "apigateway:GET",
                "apigateway:PATCH",
                "apigateway:POST",
                "apigateway:PUT",
                "cloudformation:CreateChangeSet",
                "cloudformation:CreateStack",
                "cloudformation:DeleteChangeSet",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeChangeSet",
                "cloudformation:DescribeStackEvents",
                "cloudformation:DescribeStackResource",
                "cloudformation:DescribeStacks",
                "cloudformation:ExecuteChangeSet",
                "cloudformation:ListStackResources",
                "cloudformation:UpdateStack",
                "cloudformation:ValidateTemplate",
                "events:DescribeRule",
                "events:PutRule",
                "events:PutTargets",
                "events:RemoveTargets",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:DeleteRolePolicy",
                "iam:GetRole",
                "iam:PassRole",
                "iam:PutRolePolicy",
                "iam:UpdateAssumeRolePolicy",
                "lambda:AddPermission",
                "lambda:CreateFunction",
                "lambda:DeleteFunction",
                "lambda:GetAccountSettings",
                "lambda:GetAlias",
                "lambda:GetEventSourceMapping",
                "lambda:GetFunction",
                "lambda:GetFunctionConfiguration",
                "lambda:GetLayerVersion",
                "lambda:GetLayerVersionPolicy",
                "lambda:GetPolicy",
                "lambda:InvokeFunction",
                "lambda:ListAliases",
                "lambda:ListEventSourceMappings",
                "lambda:ListFunctions",
                "lambda:ListLayerVersions",
                "lambda:ListLayers",
                "lambda:ListTags",
                "lambda:ListVersionsByFunction",
                "lambda:PublishVersion",
                "lambda:RemovePermission",
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration",
                "logs:CreateLogGroup",
                "logs:DeleteLogGroup",
                "logs:DeleteSubscriptionFilter",
                "logs:DescribeLogGroups",
                "logs:FilterLogEvents",
                "logs:GetLogEvents",
                "logs:PutSubscriptionFilter",
                "s3:CreateBucket",
                "s3:DeleteBucket",
                "s3:DeleteBucketPolicy",
                "s3:DeleteBucketWebsite",
                "s3:DeleteObject",
                "s3:DeleteObjectVersion",
                "s3:GetBucketLocation",
                "s3:GetObject*",
                "s3:ListBucket",
                "s3:PutBucketPolicy",
                "s3:PutEncryptionConfiguration",
                "s3:PutObject",
                "sts:GetCallerIdentity"
            ],
            "Resource": "*"
        }
    ]
}

So the group is attached to both policies. Group permission policies

Once the group is created, add a new user to the group. Add user to the group

And don’t forget to copy the credentials

Instance

You may now login to AWS as the new user. Next we need a machine from which we can clone the serverless code and deploy it, for this purpose I would be launching a cloud 9 instance, so that I can also use it as an online editor. You can use any Linux machine though.

I’d be creating a Cloud9 environment in the Mumbai region, ap-south-1. Launch cloud9 I’ve choosen t3.small as the instance type, we may go with t2.micro if we want a free tier eligible instance.

Once it’s created you shoud see the console. Cloud9 environment console

AWS CLI

AWS CLI comes pre-installed in Cloud9 and it would also have the credentials file

serverless-user-1:~/environment $ ls ~/.aws/
credentials

We can setup the config though.

serverless-user-1:~/environment $ cat ~/.aws/config 
[default]
region=ap-south-1

Clone

Let’s clone the alexa skill repo space-events-trivia from github.

serverless-user-1:~/environment $ git clone https://github.com/networkandcode/space-events-trivia.git

The code we cloned is a set of nodejs functions and is compatible with the serverless sdk.

serverless-user-1:~/environment $ cd space-events-trivia/
serverless-user-1:~/environment/space-events-trivia (main) $ ls
AplRender.js                   GetAnswerIntentHandler.js  interceptors.js          README.md                      StartTriviaHandler.js
CancelAndStopIntentHandler.js  HelpIntentHandler.js       LaunchRequestHandler.js  RepeatIntentHandler.js         triviaFunctions.js
documents                      index.js                   package.json             serverless.yml
ErrorHandler.js                IntentReflectorHandler.js  package-lock.json        SessionEndedRequestHandler.js

The file documents/questions.json contains the list of questions for the trivia, you may modify it as required.

Packages

The package.json file should tell us the packages we would be using in our code. package.json

Let’s install those.

serverless-user-1:~/environment/space-events-trivia (main) $ npm i
npm WARN deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated uuid@3.3.2: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.

added 18 packages, and audited 19 packages in 2s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Serverless

We can install the serverless SDK and later deploy the code using it to AWS lambda. We are actually talking about two things here, one the serverless framework/SDK, and two the serverless technology offering from AWS which is Lambda. Instead of writing the functions directly on Lambda we are going to use the serverless SDK with it’s constructs to acheieve the purpose in a controlled way.

serverless-user-1:~/environment $ npm i serverless -g

It should now be installed.

serverless-user-1:~/environment $ serverless -v
Framework Core: 3.18.2
Plugin: 6.2.2
SDK: 4.3.2

There is a serverless.yml file in our repo, that has our serverless configuration, you may modify it according to your setup.

serverless-user-1:~/environment/space-events-trivia (main) $ cat serverless.yml 
app: space-events-trivia
service: space-events-trivia

frameworkVersion: '3'

provider:
  environment:
    DYNAMODB_REGION: ${aws:region}
    DYNAMODB_TABLE: ${self:service}-users-${sls:stage}
  iam:
    role:
      statements:
      - Effect: 'Allow'
        Action:
        - 'dynamodb:CreateTable'
        - 'dynamodb:PutItem'
        - 'dynamodb:Get*'
        - 'dynamodb:Scan*'
        - 'dynamodb:UpdateItem'
        - 'dynamodb:DeleteItem'
        Resource: arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/${self:service}-users-${sls:stage}
  name: aws
  region: ap-south-1
  runtime: nodejs16.x
  stage: dev

functions:
  handler:
    handler: index.handler
    events:
    - alexaSkill: amzn1.ask.skill.${param:alexaSkillId}

The org is missing in the configuration, we can specify that while deploying. We have also given permissions for the lambda function on DynamoDB incuding the create table permission, so that the lambda function can create the table if it doesn’t exist.

Deploy

Let’s deploy the function, for which first login to serverless.

$ serverless login
? Which would you like to log into? Serverless Framework Dashboard
Logging into the Serverless Dashboard via the browser
If your browser does not open automatically, please open this URL:
https://app.serverless.com?client=cli&transactionId=<some-id>

Once logged in via the browser, you should see this screen. Serverless logged in

That’s it, time to deploy.

$ serverless deploy --org <your-org> --param="alexaSkillId=<id>"

You have to mention your org and alexaSkillId before running the command above. The org name is something you created in serverless while signing up the account, and you can get the skill ID from the alexa developer console, so that it gets mapped with alexaSkill: ${param:alexaSkillId} in serverless.yml Copy skill id

The function should get successfully deployed by now. The serverless dashboard should show a successful status with green color. Service deployed

Endpoints

You can get the lambda endpoint straight from the Serverless CLI.

$ serverless info --param="alexaSkillId=<your-skill-id>" --verbose
service: space-events-trivia
stage: dev
region: ap-south-1
stack: space-events-trivia-dev
functions:
  handler: space-events-trivia-dev-handler

Stack Outputs:
  HandlerLambdaFunctionQualifiedArn: <lambda-arn>
  EnterpriseLogAccessIamRole: <role-name>  
  ServerlessDeploymentBucketName: <bucket-name>

Want to ditch CloudWatch? Try our new console: run "serverless --console"

You can copy the lambda ARN from the output above except the suffix which is a number, and set it as an endpoint in the Skill settings in Alexa developer console. It should have the format arn:aws:lambda:<region>:<account-id>:function:<function-name>. I’ve just added it for the default region. Set endpoint

The skill could now be tested from the test window. Skill test

You can also test :test_tube: the skill from an Alexa device that’s using the same account as your developer account.

Alright then, that’s it for now, thanks for reading !!!

Image credit: unsplash