Today I want to play with Alexa and serverless framework. I’ve created a sample hello world skill. In fact this example is more or less the official hello-world sample.
const Alexa = require('ask-sdk-core')
const RequestInterceptor = require('./interceptors/RequestInterceptor')
const ResponseInterceptor = require('./interceptors/ResponseInterceptor')
const LaunchRequestHandler = require('./handlers/LaunchRequestHandler')
const HelloWorldIntentHandler = require('./handlers/HelloWorldIntentHandler')
const HelpIntentHandler = require('./handlers/HelpIntentHandler')
const CancelAndStopIntentHandler = require('./handlers/CancelAndStopIntentHandler')
const SessionEndedRequestHandler = require('./handlers/SessionEndedRequestHandler')
const FallbackHandler = require('./handlers/FallbackHandler')
const ErrorHandler = require('./handlers/ErrorHandler')
let skill
module.exports.handler = async (event, context) => {
if (!skill) {
skill = Alexa.SkillBuilders.custom().
addRequestInterceptors(RequestInterceptor).
addResponseInterceptors(ResponseInterceptor).
addRequestHandlers(
LaunchRequestHandler,
HelloWorldIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
FallbackHandler).
addErrorHandlers(
ErrorHandler).
create()
}
return await skill.invoke(event, context)
}
It has one Intent that answers to hello command.
const HelloWorldIntentHandler = {
canHandle (handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'HelloWorldIntent'
},
handle (handlerInput) {
const speechOutput = 'Hello World'
const cardTitle = 'Hello world'
return handlerInput.responseBuilder.
speak(speechOutput).
reprompt(speechOutput).
withSimpleCard(cardTitle, speechOutput).
getResponse()
}
}
module.exports = HelloWorldIntentHandler
I’m using Serverless framework to deploy the skill. I know that serverless frameworks has plugins for Alexa skills that help us to create the whole skill, but in this example I want to do it a little more manually (it’s the way that I learn new things).
First I create the skill in the Alexa developer console (or via ask cli). There’re a lot of tutorials about it. Then I take my alexaSkillId and I use this id within my serverless config file as the trigger event of my lambda function.
service: hello-world
provider:
name: aws
runtime: nodejs8.10
region: ${opt:region, self:custom.defaultRegion}
stage: ${opt:stage, self:custom.defaultStage}
custom:
defaultRegion: eu-west-1
defaultStage: prod
functions:
info:
handler: src/index.handler
events:
- alexaSkill: amzn1.ask.skill.my_skill
then I deploy the lambda function
npx serverless deploy –aws-s3-accelerate
And I take the ARN of the lambda function and I use this lambda as my endpoint in the Alexa developer console.
Also we can test our skill (at least the lambda function) using our favorite testing framework. I will use jest in this example. Testing is very important, at least for me, when I’m working with lambdas and serverless. I want to test my script locally, instead of deploying to AWS again and again (it’s slow).
const when = require('./steps/when')
const { init } = require('./steps/init')
describe('When we invoke the skill', () => {
beforeAll(() => {
init()
})
test('launch intent', async () => {
const res = await when.we_invoke_intent(require('./events/use_skill'))
const card = res.response.card
expect(card.title).toBe('Hello world')
expect(card.content).toBe('Welcome to Hello world, you can say Hello or Help. Which would you like to try?')
})
test('help handler', async () => {
const res = await when.we_invoke_intent(require('./events/help_handler'))
console.log(res.response.outputSpeech.ssml)
expect(res.response.outputSpeech.ssml).toBe('<speak>You can say hello to me! How can I help?</speak>')
})
test('Hello world handler', async () => {
const res = await when.we_invoke_intent(require('./events/hello_world_handler'))
const card = res.response.card
expect(card.title).toBe('Hello world')
expect(card.content).toBe('Hello World')
})
})
Full code in my github account.