Improving the UX of your website with an intelligent chatbot | AWS Lex

Note: The video series of this blog post is available here.

User Experience is one of the most important concerns in building modern web applications. No matter how feature-rich is your website, if people don’t find it intuitive to use, you will not reach out to your potential customers.

AWS Lex allows users to easily interact with your website using natural language via a conversational chatbot. Your users can chat in voice or in text and use your product services without having to go through complex user interfaces.

As a developer, you don’t have to be a Machine Learning/Deep Learning expert to have a chatbot embedded into the website. AWS Lex provides advance deep learning functionalities and automatic speech recognition techniques out of the box.

In this post, we are going to create a conversational bot that finds weather information as per user’s requests. Following are the technologies that we will be using in this project.

  1. Amazon Lex
  2. Amazon Cognito
  3. AWS Lambda
  4. AWS IAM
  5. Amplify Library
  6. Angular Framework

Please find the github repo related to this post at

Getting the bot ready for training

First, let’s create our bot and get it ready for training. We want our bot to search for weather information when a user has requested it. In this guide, let’s only consider communication via text.

Login to AWS console and Go to AWS Lex

If you haven’t created a bot before select get started and you will be directed to the create bot window.

Creating a Custom Bot

We can either select already created bot or create our custom bot. Let’s select Custom Bot.

Let’s give our bot a name(I named it “WeatherBot”)and fill the other configurations as above image. Since we don’t enable voice select text-based application option. Afterward, click create and you will be presented with a new screen as below.

We need to understand the terminology for our bot in AWS Lex. There are 5 main points to remember.

  1. Intents
  2. Utterances
  3. Slots
  4. Prompts
  5. Fulfillment

Intents are the intentions why someone would use the bot. In our example, someone might want to know the weather of a particular city in the world. We will have an intent called “FindWeather”. We can have more than one intents for the bot. Another intent would be “GreetUser”. Utterances are how a user interacts with the bot stating different phrases. An example utterance can be “How is the weather in Colombo?”

When a user utters a phrase in text or invoice, the bot will match it to a corresponding intent. “How is the weather in Colombo” utterance will be matched to “FindWeather” intent. In order to fulfill the user’s intent, the bot will ask other required questions from the user. These questions are called “Prompts”. Once a user replied to a prompt that will be stored in variables for later use. These variable are called “Slots”. When all the required slots for a intent is collected, the bot will fulfill the intention of the user. The “fulfillment” may involve calling a 3rd party service to search for information, talking to a database, executing some logic in lambda function etc… Our example WeatherBot will talk to OpenWeatherMap API with the user requested city to search for weather information and send it back to the user.

Creating Intents

Click “Create Intent” button to create the first intent for our WeatherBot. Let’s call it “FindWeather” and Add it.

Now let’s add some sample utterances a user might ask. They will help our chatbot to learn about user inputs.

I have added three utterances as shown above. Note that two of those utterances has {City} variable. This is a required slot value for FindWeather intent. We can fill the slot using the user’s utterance itself. “Tell me about the weather” utterance doesn’t have {City} slot involved. So the bot will question the user about the city using a prompt.

In the Slots section, let’s add our City slot. We can define the Prompt message together with that. Our bot can use the intent to get the City slot filled from the user if it didn’t receive it already from the user’s initial utterance.

Our bot requires only one variable to search for the weather. That is the City. Once it is received from the user, the bot will call the action for Fulfillment of the intent.

In our case, let’s call a Lambda function that talks to OpenWeather API to search the weather for the requested City.

Creating the Lambda Function

Let’s use the Serverless Framework to create our lambda function. If you haven’t already installed Serverless Framework please visit this link.

Once you have installed the serverless framework and configure credentials with your AWS account, create a serverless service using below command.

serverless create --template aws-nodejs --path weather-bot

Once creation is completed, change directory into weather-bot folder and open the files in your favorite IDE.


Let’s add a simple function as shown above. I will call it getWeather. The logic of the function lies in the handler.js file.

Lambda logic to obtain weather info from OpenWeatherMap API

I have obtained a free API key from and added it as a query parameter(APPID) in the URL. I extracted the “City” slot value that was taken by the user and passed it in the URL as well. “units” parameter is set to “metric” in order to get temperature values in Celsius.

I also used “axios” npm library to easily send HTTP requests to the server. You may also use the default “http” node module if you prefer. Note how the answer variable is constructed. It is created with string concatenation to form a natural language like the response. This response is returned from the lambda function as a special JSON object. This is required for our bot to read the answer properly. For more information about request/response JSON object template supported by AWS Lex see this link from official documentation.

Alright! Let’s deploy our lambda function using the following command. Before that, don’t forget to install axios library from npm

// First run
npm install axios
// Then run
serverless deploy

Once it is successfully deployed, go back to Lex console and select the lambda function name under the Fulfillment section.

Testing our bot

We have done all the configuration required from our side. Now let’s build the bot and let AWS Lex train its neural network. Once the build is complete we can test our bot.

Once it showed the success message, you can start testing it in the console itself.

The bot further asks a question since we didn’t provide it the city name.

As you can see, our bot successfully connected to OpenWeatherMap API sent us the Weather information for London city.

Adding our bot to the website

Now that we have an awesome bot, we need to add it to our production website so our users can directly interact with it.

Let’s create an angular website and use AWS Amplify library to connect to our WeatherBot securely. We need to install angular CLI and amplify CLI globally and configure amplify to with AWS credentials.

// Install angular cli globallynpm install -g @angular/cli
// Create a new angular projectng new my-bot-website --style=scss --routing
// Install amplify library globally
npm install -g @aws-amplify/cli
// Configure amplify with AWS IAM credentials
amplify configure

In order to communicate with our bot securely, we have to make sure only the logged in user can talk to it. As for the next steps let’s add a Login to the website and then use Amplify out of the box interaction component to connect to our WeatherBot.

Once a user is successfully logged into the application, AWS Cognito assigns him an IAM role. The Permission/Policies for invoking backend services are associated with this role. Since our user will communicate with the AWS Lex Chatbot, we need to provide permission for that authenticated role to call AWS Lex services. You can find the IAM role name that is assigned to logged in user at Cognito Identity Pool configuration.

I’m not going to add those steps in this blog as this post is already lengthy. Instead, let me share the Github URL for the code here.

You can clone the code from the repo and run amplify init to initialize with your AWS resources. To run it on browser use amplify serve

You can use Amplify library itself to host your website along with the bot in an S3 bucket.

I hope someone will find this post useful.


Please follow and like us:

Building a Talking App | AI/ML Series

Welcome to another practical AWS tutorial. This is the written version of following YouTube video. I would recommend you to watch the video before reading the blog. Use this blog as the source to copy the code and practice by building the app by yourself.

AWS services used in the App

  • Amazon Polly
  • Amazon S3
  • AWS Lambda

Creating a Serverless Project/Service

Install serverless framework by with npm and create a new nodejs project/service called backend.

npm install serverless -g
serverless create --template aws-nodejs --path backend

Now replace to serverless.yml file with following code, that creates a lambda function called “speak”.

service: talking-backend 

name: aws
runtime: nodejs8.10
region: us-east-1
role: arn:aws:iam::<account-id>:role/talking-app-role

handler: handler.speak
- http:
path: speak
method: post
cors: true

The “speak” lambda function will send the text payload to AWS Polly and return the voice file from S3 bucket.

Creating a S3 Bucket

We need a S3 bucket to store all the voice clips that is returned by AWS Polly. Use AWS console to create the bucket with a unique name. In my case S3 bucket name is “my-talking-app”.

Create an IAM Role

Serverless framework creates two Lambda functions that interact with AWS Polly and AWS S3 services. (We shall see the code later in the blog). In order to communicate with these services, our Lambda function must be assigned an IAM role that has permission to talk to S3 and Polly. So create an IAM role with a preferred name i.e. “talking-app-role” with the following IAM policy.

    "Version": "2012-10-17",
    "Statement": [
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
            "Resource": "*"
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [

Copy the ARN of the IAM role and add it under the provider section of the serverless.yml file.

name: aws
runtime: nodejs8.10
region: us-east-1
role: arn:aws:iam::885121665536:role/talking-app-role

“Speak” Lambda Function

Speak Lambda function does three main tasks.

  1. Call AWS Polly synthesizeSpeech API and get the audio stream (mp3 format) for text that user entered
  2. Save the above audio stream in the S3 bucket
  3. Get a signed URL for the saved mp3 file in the S3 and send it back to the frontend application

First of all, let’s install the required npm modules inside the backend folder.

npm install aws-sdk 
npm install uuid

AWS Polly synthesizeSpeech API requires the text input and the voice id to convert the text into speech. Here, we use the voice of “Joanna” to speak the text that is passed from the frontend.

let AWS = require("aws-sdk");
let polly = new AWS.Polly();
let s3 = new AWS.S3();
const uuidv1 = require('uuid/v1');

module.exports.speak = (event, context, callback) => {
let data = JSON.parse(event.body);
const pollyParams = {
OutputFormat: "mp3",
Text: data.text,
VoiceId: data.voice

// 1. Getting the audio stream for the text that user entered
.on("success", function (response) {
let data =;
let audioStream = data.AudioStream;
let key = uuidv1();
let s3BucketName = 'my-talking-app';

// 2. Saving the audio stream to S3
let params = {
Bucket: s3BucketName,
Key: key + '.mp3',
Body: audioStream
.on("success", function (response) {
console.log("S3 Put Success!");
.on("complete", function () {
console.log("S3 Put Complete!");
let s3params = {
Bucket: s3BucketName,
Key: key + '.mp3',

// 3. Getting a signed URL for the saved mp3 file
let url = s3.getSignedUrl("getObject", s3params);

// Sending the result back to the user
let result = {
bucket: s3BucketName,
key: key + '.mp3',
url: url
callback(null, {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin" : "*"
body: JSON.stringify(result)
.on("error", function (response) {
.on("error", function (err) {
callback(null, {
statusCode: 500,
headers: {
"Access-Control-Allow-Origin" : "*"
body: JSON.stringify(err)

Now, deploy the backend API and the Lambda function

sls deploy

Frontend Angular App

In order to test our backend we need a frontend that makes speak request with user inputted text. So let’s create an angular application.

ng new client
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? SCSS

Let’s create an angular service that talks to our Amazon Polly backend.

ng g s API

Add the following code to the api.service.ts file. It will create speak function that call the lambda function with the selected voice and the inserted text by the user.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

providedIn: 'root'
export class APIService {


constructor(private http:HttpClient) {}

speak(data) {
return, data);

Let’s use the main appcomponent to render our UI for the “Talking App”. Goto app.component.html and replace the file with following HTML code. It will add a basic text-area, selection of preferred voice and speak action button.

<div style="margin: auto; padding: 10px; text-align: center;">
<h2>My Talking App</h2>
<textarea #userInput style="font-size: 15px; padding: 10px;" cols="60" rows="10"></textarea>
<select [(ngModel)]="selectedVoice">
<option *ngFor="let voice of voices" [ngValue]="voice">{{voice}}</option>
<div style="margin-top: 10px">
<button style="font-size: 15px;" (click)="speakNow(userInput.value)">Speak Now</button>

Goto app.component.ts file and add the corresponding hander function for the view. Replace it with the following code.

import { Component } from '@angular/core';
import { APIService } from './api.service'

selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
export class AppComponent {
voices = ["Matthew", "Joanna", "Ivy", "Justin"];
selectedVoice = "Mattew";

constructor(private api: APIService){}

let audio = new Audio();
audio.src = url;

let data = {
text: input,
voice: this.selectedVoice
this.api.speak(data).subscribe((result:any) => {

Since we are using, ngModel in the app.component.html we need to import the FormsModule in the app.module.ts file. Goto app.module.ts file and replace the content with,

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';
declarations: [
imports: [
providers: [],
bootstrap: [AppComponent]
export class AppModule { }

Running the Application

Now that our backend and the frontend are ready, let’s play with our app.

Go to the client directory and run the angular app locally,

ng serve

Type some text on the text area and select a voice from the dropdown. When you click Speak Now it should speak the text aloud!


Please follow and like us:

Building a Profile App – Part 01

This blog post is connected to the following youtube video. I would recommend you to watch the video first and use this blog post to copy the code snippets and build the application by yourself.

Watch the video here

Creating the Angular App

Let’s start a new angular application using ng new command.

ng new profileApp

Select YES for angular routing and select SCSS when prompted from the CLI. After the project is created, change directory into the profileApp folder by,

cd profileApp

Now let’s create two components for Login page and Profile landing page.

ng g c auth
ng g c profile

Installing Amplify Libraries

It’s time to add amplify and aws-appsync libraries. Firstly, install the amplify cli globally and configure it with your AWS account.

npm install -g @aws-amplify/cli
amplify configure

Afterwards, we need to install amplify, amplify-angular, app-sync and graphql-tag libraries as we are to use them in our profile app.

npm install --save aws-amplify
npm install --save aws-amplify-angular

Additional configuration for the Angular App

We need to add some polyfills and additional configurations to get amplify and appsync work with our angular application. Otherwise you’ll waste much time troubleshooting errors.

In the polyfills.ts file (src/polyfills.ts) add following two lines on top of the file.

(window as any).global = window; 
(window as any).process = { browser: true };

Also goto index.html (src/index.html) and add the following script within the head tags.

if(global === undefined) {
var global = window;

Now goto (src/ and add “node” as the compilerOptions type.

"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": ["node"]
"exclude": [

Initializing an Amplify Project on Cloud

At this point, we can initialize an amplify project using the amplify cli.

amplify init
## Provide following answers when prompted

? Enter a name for the project profileApp
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project? What javascript framework are you using angular
? Source Directory Path: src
? Distribution Directory Path: dist/profileApp
? Build Command: npm run-script build
? Start Command: ng serve
## Choose your aws profile when prompted as well

After the process is completed, let’s add two amplify categories for auth and api.

amplify add auth
## Provide following answer for the prompt

Do you want to use the default authentication and security configuration? Yes, use the default configuration.
amplify add api
## Provide following answers for the prompts

? Please select from one of the below mentioned services GraphQL
? Provide API name: profileapp
? Choose an authorization type for the API Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project
? Do you have an annotated GraphQL schema? No
? Do you want a guided schema creation? Yes
? What best describes your project: Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to edit the schema now? Yes

Now, amplify will open the schema.graphql file with sample model. While command prompt is open, replace the content with the following graphql model and save the file and press enter to continue in the command prompt.

type User @model {
id: ID!
username: String!
firstName: String
lastName: String
bio: String
image: String

At this point, we have created the templates for all the AWS resources locally. We need to push the template and actually create the services. To do that type,

amplify push
## Provide following answers when prompted

? Are you sure you want to continue? Yes
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target angular
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/*/.graphql
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
? Enter the file name for the generated code src/app/API.service.ts

It will take few minutes to provision the resources on AWS. Be patient 🙂

Configuring Amplify Libraries with the App

Now that we have configured the resources on AWS, it creates a new file i.e. aws-exports.js file with all the configuration details of those services in the frontend directory structure.

Let’s use that file and setup the initiate connection from Angular frontend to AWS backend.

Goto main.ts (/src/main.ts) file and configure amplify.

import Amplify from 'aws-amplify';
import amplify from './aws-exports';


Now let’s import amplify-angular library to use the already configured higher order components for our login.

Goto app.module.js and import AmplifyAngularModule and AmplifyService.

import {AmplifyAngularModule, AmplifyService} from 'aws-amplify-angular';

declarations: [
imports: [
providers: [AmplifyService]

Now we can use <amplify-authenticator></amplify-authenticator> component directly in the auth component html and implement a complete login functionality. (Magical!)

But before that let’s setup our routes in app-routing.module.js file. We have two basic routes. One for the login screen and the other for our profile component.

In the app-routing.module.ts file add the routes.

 const routes: Routes = [{
path: "profile",
component: ProfileComponent
path: "login",
component: AuthComponent
path: '**',
redirectTo: 'login',
pathMatch: 'full'

Adding the Login Component

It’s time to add the login screen. Goto auth.component.html and add this code. It will turn in to a login screen.


Before running the application to check login screen, you need to update the styles in of amplify-authenticator in styles.scss file.

Add this line of css in the style.scss file (src/styles.scss)

@import '~aws-amplify-angular/theme.css';

We need to remove the default content that angular has added in app.component.html file. So let’s do that too. Your app.component.html should look like this, when you remove the default code


Okay. Now let’ run ng serve and check the output!

Figure 01 – Login Page

Styling with MDBootStrap

Now we need to build the Profile component. But before that, let’s configure MDBootStrap with our project to add styles to the profile component easily.

npm i angular-bootstrap-md --save

npm install -–save chart.js@2.5.0 @types/chart.js @types/chart.js @fortawesome/fontawesome-free hammerjs 

To app.module.ts add,

import { MDBBootstrapModule } from 'angular-bootstrap-md'; 

@NgModule({ imports: [ MDBBootstrapModule.forRoot() ] });

In the angular.json file replace styles and scripts sections with,

"styles": [
"scripts": [

Adding the Profile Component

Now let’s edit the profile component. In profile.component.html, add following html code,

<!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-dark default-color">
<a class="navbar-brand" href="#"><strong>Profile</strong></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="#"> Hello {{userName}}!</a>
<li class="nav-item active">
<a class="nav-link"> Logout <span class="sr-only">(current)</span></a>

<!-- Main Content -->
<main class="text-center my-5">
<div class="container">
<h2>My Profile</h2>
<div class="form-group row">
<label for="firstName" class="col-sm-2 col-form-label">First Name</label>
<div class="col-sm-10">
<div class="md-form mt-0">
<input type="text" class="form-control" id="firstName" name="firstName" [(ngModel)]="user.firstName">
<div class="form-group row">
<label for="lastName" class="col-sm-2 col-form-label">Last Name</label>
<div class="col-sm-10">
<div class="md-form mt-0">
<input type="text" class="form-control" id="lastName" name="lastName" [(ngModel)]="user.lastName">
<div class="form-group row">
<label for="aboutMe" class="col-sm-2 col-form-label">About Me</label>
<div class="col-sm-10">
<div class="md-form mt-0">
<textarea id="aboutMe" name="aboutMe" [(ngModel)]="user.aboutMe" class="form-control md-textarea" length="120"
<div class="form-group row">
<div class="col-sm-3">
<button type="submit" class="btn btn-primary btn-lg" (click)="updateProfile()">Update</button>

In the form, we data binding to a model called user. Let’s add that model and import it to the profile.component.ts file.

// Generate a typescript class
ng g class User

Add following code to user.ts,

Since we need to use ngModel in the profile component, we should import FormsModule into the app.module.js

import { FormsModule} from '@angular/forms';

imports: [

Okay, now we need to implement updateProfile() function to grab the data from the form and store the data in the DynamoDB table.

export class User {
public id: string,
public username: string,
public firstName: string,
public lastName: string,
public aboutMe: string,
public imageUrl: string

In the profile.component.js file add,

import { Component, OnInit } from '@angular/core';
import { APIService } from '../API.service';
import { User } from '../user';
import { Auth } from 'aws-amplify';

selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss']

export class ProfileComponent implements OnInit {
userId: string;
userName: string;
user = new User('', '', '', '', '', '');

constructor(private api: APIService) {}

ngOnInit() {
bypassCache: false
}).then(async user => {
this.userId = user.attributes.sub;
this.userName = user.username;
.catch(err => console.log(err));

async updateProfile() {
const user = {
id: this.userId,
username: this.user.firstName + '_' + this.user.lastName,
firstName: this.user.firstName,
lastName: this.user.lastName,
bio: this.user.aboutMe
await this.api.CreateUser(user);

updateProfile function will get the firstName, userName, lastName, and bio information from the form inputs.

But the “id” attribute has to be taken from currently authenticated user.

Now, let’s run ng serve and goto “/profile” path to view our profile page.

Figure 02 – Profile Page

In the second part of this blog we are going to add following functionalities to our profile app.

  • Loading the saved user data
  • Ability to securely upload the profile image
  • Adding auth guard for the profile component so that unauthorized users will not have access to profile page
  • Automatically redirecting to profile page after a successful login

So guys, I hope this has been useful to you. I’ll see you in the next part.

Stay tuned!

Please follow and like us: