A Framework for creating Amazon Echo (Alexa) skills using Node.js
const alexia = require('alexia');
const app = alexia.createApp();
app.intent('HelloIntent', 'Hello', () => {
return 'Hello from Alexia app';
});
HTTPS Server
app.createServer().start();
or
AWS Lamba
exports.handler = (event, context, callback) => {
app.handle(event, data => {
callback(null, data);
});
};
npm install alexia --save
Optional: requires Handling Amazon Requests manually
npm install hapi --save
Alexia helps you to write Amazon Echo skills using Node.js. This framework handles Amazon Echo requests and automatically calls intents in your application. See the Features and Samples
Creating new skills for Amazon Echo using alexia requires you to understand some basic terms. This part should clarify the most of them.
utterances
is recognizedTo create new app simply call alexia.createApp()
const alexia = require('alexia');
const app = alexia.createApp('MyApp');
If you want to set default value of shouldEndSession response property you can do it by specifying shouldEndSessionByDefault
property in App options.
const app = alexia.createApp('MyApp', {shouldEndSessionByDefault: true});
Alternatively you can use app.setShouldEndSessionByDefault()
method.
app.setShouldEndSessionByDefault(true);
You have to give a name for the intent when you are creating it. Also you can set multiple utterances for any of your intents.
// Named intent
app.intent('MyIntent', 'Hello Alexa my name is Michael', () => 'Hi Michael');
// Intent with more utterances
app.intent('AnotherIntent', ['Hello', 'Hi', 'Whats up'], () => 'Hello yourself');
If you want more than just a generic "Welcome" from Alexa, you can use the onStart method to help you achieve that.
app.onStart(() => {
return 'Welcome to My Hello World App, say hello world to get started, or say help to get more instructions';
});
Amazon Alexa Skills Kit provides a collection of built-in intents. These are intents for very common actions. Alexia provides convenient methods for their reusing and extending.
List of built-in intents: cancel
, help
, next
, no
, pause
, previous
, repeat
, resume
, startOver
, stop
, yes
.
See official Amazon docs: [Available Built-in Intents](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/implementing-the-built-in-intents#Available Built-in Intents)
// Use default built-in utterances
app.builtInIntent('stop', () => 'Stopping now');
// Extend built-in utterances
app.builtInIntent('stop', 'Stop now', () => 'Stopping now');
app.builtInIntent('stop', ['Stop now', 'Please stop'], () => 'Stopping now');
As mentioned in Terminology section - slots represent variable part of user input in utterances. To make their creation bit easier our utterances contain slot name with type. These samples are converted into common utterances recognized by Alexa and slots are included in intentSchema.
app.intent('SlotIntent', 'My number is {num:Number}', (slots) => {
return `Your number is ${slots.num}`;
});
Alexia helps you to create custom slots by specifying its name
and utterances
app.customSlot('Name', ['Arnold', 'Otto', 'Walda', 'Pete']);
app.intent('CustomSlotIntent', 'My name is {name:Name}', (slots) => {
return `Hi ${slots.name}`;
});
Intent can be resolved using simple string (a text response) or more complex responseObject
. Its attribute attrs
will override current sessionAttributes. If you wish to extend current session attributes you can use for example Object.assign
method. Make sure you set end
attribute to false
to keep the session open (default: true
). See Session Attributes example. Session attribute previousIntent
is reserved.
app.intent('AttrsIntent', 'session attributes test', (slots, attrs) => {
return {
text: 'Alexa response text here',
attrs: {
attr1: 'Whatever to be remebered in this session'
},
end: false
};
});
To display card in Alexa app add configuration to responseObject card
property
app.intent('CardsIntent', 'Whats in shopping cart', () => {
return {
text: 'Your shopping cart contains Amazon Echo Device and 2 more items. To see the full list check out your Alexa app',
card: {
title: 'Shopping cart',
content: 'You shopping cart contains: Amazon Echo, Amazon Tap, Echo Dot'
}
};
});
To add reprompt text to your response add reprompt
string value to responseObject
app.intent('RepromptIntent', 'Send email to Mom', () => {
return {
text: 'What is the text of your message',
reprompt: 'Sorry I did not catch it. What is the text of your message'
};
});
Use SSML to create more complex text responses. Just set the ssml
parameter of responseObject to true
and enter SSML
into text
property. See official Amazon docs: Speech Synthesis Markup Language
app.intent('SSMLIntent', 'what are the digits of number {num:Number}', (slots) => {
return `<say-as interpret-as="digits">${number}</say-as>`
});
You can access the original Amazon request data from third parameter of handler. See example below.
app.intent('OriginalRequestData', 'read original request data', (slots, attrs, data) => {
console.log('userId', data.session.user.userId);
return 'Hi';
});
For asynchronous intent handling add fourth parameter to your handler callback and call it when your response is ready. The response structure is identical to responseObject.
app.intent('AsyncIntent', 'Search for something in database', (slots, attrs, data, done) => {
setTimeout(() => {
done('Work complete');
}, 120);
});
To minimize manual work needed while deploying your Alexa skills you can use our speechAssets generator. This helps you to create intentSchema
, sampleUtterances
and customSlots
for your apps.
Speech assets consists of:
customSlots - custom slot types with samples
For more information see interaction model reference
const speechAssets = app.speechAssets(); // object
console.log(speechAssets.toString()); // stringified version - f.e. copy paste from console
If you want to use your assets (intentSchema
, sampleUtterances
and customSlots
) later and have them stored, this function will do it for you. You can pass the name of your directory or leave it empty which defaults to /speechAssets
.
Directory structure looks like this:
├── speechAssets
├── intentSchema.json
├── utterances.txt
└── customSlots
├── Name.txt
├── Age.txt
...
app.saveSpeechAssets('speechAssets'); // No argument leads to default value 'speechAssets'
If your intents are located in separate files you need to register them to the app. One way how to do this is to wrap intent into function taking app
as a parameter.
src/intents/hello-intent.js
module.exports = app => app.intent('HelloIntent', 'hello', () => {
return 'Hello';
});
Next you need to register it by importing it manually and supplying the app
as a parameter.
You can also use our shorthand function for finding and registering all intents files that match pattern. See node-glob for more pattern matching examples.
src/app.js
app.registerIntents('src/intents/*-intent.js');
Feature of Alexia that helps you to control flow of the intents. To understand it easier see the code below.
By defining the action you enable transition from one intent to another. When no actions are specified, every intent transition is allowed.
Action properties from
and to
can be defined as string
(one intent), array
(multiple intents) or '*'
(all intents).
Each action could have condition to check whether the transition should be handled or the fail method should be invoked. If no fail method is defined app.defaultActionFail()
is invoked when condition of handling is not met or the action (transition) is not defined.
// Allow transition from any intent to `intent1`.
app.action({
from: '*',
to: 'intent1'
});
// Allow transition from `@start` intent to `intent2`.
app.action({
from: '@start',
to: 'intent2'
});
// Allow transition from `intent1` to `intent2` if condition is met using custom fail handler
app.action({
from: 'intent1',
to: 'intent2',
if: (slots, attrs) => slots.pin === 1234,
fail: (slots, attrs) => 'Sorry, your pin is invalid'
});
// Allow transition from `intent2` to `intent3` and also `intent4`.
app.action({
from: 'intent2',
to: ['intent3', 'intent4']
});
// Set default fail handler
app.defaultActionFail(() => 'Sorry, your request is invalid');
Alexia uses i18next for localizing response texts, utterances and custom slots.
For better understanding see localized app example: examples/multi-language.
These are the steps required to localize your existing application:
npm install --save i18next i18next-node-fs-backend
i18next
instance - see the example appi18next
instance to your app to enable localization: app.setI18next(i18next)
app.t('key')
Localized intent example:
app.intent('LocalizedIntent', slots => {
return app.t('text', slots);
});
Example locales
directory structure:
locales/
├── en/ # Directory for all en locales
│ ├── translation.js # Translations of response texts and utterances for each intent
│ └── custom-slots.js # Translations of custom slots
└── de/ # Directory for all de locales ...
├── translation.js
└── custom-slots.js
Localization notes:
LaunchRequest
or SessionEndedRequest
as well. Just add the entry along the intent names in translationsAMAZON.YesIntent
use entry names after the .
suffix. So AMAZON.YesIntent
becomes just YesIntent
app.t('key')
This key
needs to be nested in the current intent translation entry. You don't have to use the full path to the key - the prefix is automatically added depending on the current requestutterances
property. We support the richUtterances
syntax f.e: My age is {age:Number}
data.request.locale
Its value could be currently one of: en-US
, en-GB
, en-CA
, en-IN
, en-AU
, de-DE
, fr-FR
, ja-JP
To handle Amazon requests you need to create HTTP server with POST route. You can take advantage or our API to create Hapi server so you don't have to create it manually. This requires to install hapi
as dependency:
npm install hapi --save
const options = {
path: '/', // defaults to: '/'
port: 8888 // defaults to: process.env.PORT or 8888
};
const server = app.createServer(options);
You can create your own HTTP from scratch to handle Amazon requests manually. See below example with Hapi server
const Hapi = require('hapi');
const server = new Hapi.Server();
const app = require('./app'); // Your app
server.connection({
port: process.env.PORT || 8888
});
server.route({
path: '/',
method: 'POST',
handler: (request, response) => {
app.handle(request.payload, (data) => {
response(data);
});
}
});
server.start((err) => {
if (err) throw err;
console.log('Server running at:', server.info.uri);
app.saveSpeechAssets();
});
start
script defined in package.json
git init
if git was not yet initialized in your projectheroku login
and enter your credentialsheroku create
in project directorygit push heroku master
index.handler
handler
in your index.js fileexports.handler = (event, context, callback) => {
app.handle(event, data => {
callback(null, data);
});
};
Set skill info required to run app:
Skill Information
Alexa, start MyApp
if your invocation name is MyApp
Interaction model
app.saveSpeechAssets()
to generate and save speech assets to speechAssets
directoryAdd Slot Type
speechAssets/customSlots/**
or enter custom slot samples manuallyspeechAssets/intentSchema.json
speechAssets/sampleUtterances.txt
Configuration
SSL Certificate
Test
Ask MyApp
Alexa, start <myApp>
Each application should be unit-tested. We are exposing simple API helping you to create sample Alexa requests for testing and debugging.
alexia.createRequest({
type: 'IntentRequest',
name: 'UnknownIntent',
slots: {},
attrs: {},
appId: 'amzn1.echo-sdk-123456',
sessionId: 'SessionId.357a6s7',
userId: 'amzn1.account.abc123',
requestId: 'EdwRequestId.abc123456',
timestamp: '2016-06-16T14:38:46Z',
locale: 'en-US',
new: false
});
All the properties optional and defaults to the values you see in the example above. Sample usage:
alexia.createRequest({type: 'IntentRequest', name: 'HelloIntent', slots: ..., attrs: ...});
alexia.createIntentRequest('HelloIntent', slots, attrs, isNew, appId); // Shorter version - does not support all of the properties
Before writing unit tests make sure to install all the dependencies. In our example we will be using mocha and chai with expect.
npm install mocha chai expect --save-dev
Example below illustrates simple unit testing for intentRequest. Testing of launchRequest or sessionEndedRequest would look the same
const expect = require('chai').expect;
const alexia = require('alexia');
const app = require('./path-to-app.js');
// Create sample requests
const launchRequest = alexia.createLaunchRequest();
const sessionEndedRequest = alexia.createSessionEndedRequest();
const intentRequest = alexia.createIntentRequest('MyIntent');
// Sample MyIntent test suite
describe('(Intent) MyIntent', () => {
it('should handle MyIntent', done => {
// Simulate Alexa request handling
app.handle(intentRequest, response => {
// Test the response
expect(response).to.be.defined;
done();
});
});
});
We are using debug package to debug our alexia applications. To start application in debug mode export environment variable DEBUG
Examples:
DEBUG=alexia:info
- print only info logsDEBUG=alexia:debug
- print only debug logsDEBUG=alexia:*
- print all logsTo start your app with info logs run in terminal:
DEBUG=alexia:info npm start
npm test
- run unit testsnpm test:dev
- run unit tests in development mode using nodemon as watchernpm run lint
- run eslintnpm run lint:fix
- run eslint and automatically fix problemsnpm run toc
- update TOC in README.mdAlexia is an open source project and we encourage contributions. Please make sure to cover your code with unit tests.
After updating README.md please run: npm run toc
For more information refer to general guide Contributing to Open Source