Have your MVP Running in Prod within 15 Minutes with Serverless
Yu Ling Cheng12 min read
I always feel guilty when I suddenly motivate myself to go to the gym, then have all the painful thoughts like going out in the cold, being sweaty and feeling stiff afterward and decide that I’d rather stay in bed watching my favorite series.
I have the same mixed feelings when I get an idea of the project and then get discouraged —even before getting started— thinking about having to provision a server and deploy my code to see it live and used by others than myself.
But recently, I discovered Serverless, a framework based on Amazon Lambda that helps you deploy your code in seconds.
The framework is said to relieve Lambda functions of its main pain points (the AWS console, the heavy configuration), to allow developers to work with more familiar standards.
Looks like a really nice promise that would dismiss all my excuses not to go on with any of my ideas:
- Focus on coding, deploy single functions in the cloud
- Don’t manage any server. AWS handles provisioning and scaling
- Pay only when the functions are running
I decided to test it on a fun project and experience how promising it actually is.
I ended up creating a chatbot game on Facebook Messenger, to help my colleagues learn the name of everybody in the company.
I started with this tutorial: Building a Facebook Messenger Chatbot with Serverless, which quickly allowed me to play with my phone talking to my chatbot.
But I have to admit I had to struggle a little to fully understand all the magic behind the framework and diverge from the tutorial to do what I wanted.
In this article, you’ll find:
What cool projects can you do with Serverless?
Lambda functions are handy for:
-
A cron job running without having a full server dedicated to it
Ex: a custom IFTTT or Zapier
-
An automatic data processing job
Ex: create thumbnails for profile pictures uploaded to your website
Funnier: a backend for a chatbot
Building a chatbot is a great mean to test an idea and develop an MVP.
The advantage is that you only have to focus on the backend, since the frontend and delivery is granted by the messaging service you’ll be using.
And with Serverless,
- Only code the logic of the bot
- Quickly iterate as deploying is super fast
- Spend little money while testing
- Keep focusing on your service if you get successful as AWS handles scaling
Having a Chatbot Running in Prod in 15 Minutes with Serverless
Requirements
Before starting the tutorial, make sure you have:
-
An account set on AWS ~3min + 24h validation
- Be aware that a credit card is required to sign up
- You’ll have the free tier for one year
- You’ll need to wait 24 hours to have your account validated
- Be patient, you can watch your favorite series or go to the gym while you wait ;)
-
Node v4 or higher to install Serverless ~1min
npm install -g serverless
will do the job -
The API Key & Secret of an IAM user ~2min
With programmatic access and AdministratorAccess. The paragraph Creating AWS Access Keys in the Serverless doc is fairly explicit for that.
-
Configured Serverless with your AWS credentials ~1min
I recommend using the
serverless config credentials
command.
You’ll avoid having to installaws-cli
or managing environment variables -
For the chatbot, a Facebook Developer account ~1min
3min if you don’t have a Facebook account yet
-
For the chatbot, a Facebook page that you own ~2min
The page gives an identity to your chatbot, you can’t have one without it.
Tutorial
Init your project
$ sls create --template aws-nodejs --path my-first-chatbot
This creates two files in the directory my-first-chatbot
:
├── my-first-chatbot
│ ├── handler.js
│ └── serverless.yml
I used Node.js for my bot. If you prefer Python, use aws-python
instead.
First take a look at the serverless.yml
file.
It is the configuration file of your project.
Lots of options are commented in the file, all you need for now is the following:
service: my-first-chatbot
provider:
name: aws
runtime: nodejs4.3
region: eu-central-1 # (Frankfort) Choose the closest data center
# from your end users
functions:
hello: # the name of your Lambda function
handler: handler.hello # The node function that is exported
# from the handler.js module
# It is used as handler by AWS Lambda
# That's to say the code excecuted
# when your Lambda runs.
So far you have declared the hello
Lambda function which will be deployed somewhere in the Frankfort AWS cloud.
You can already invoke it locally from your shell to check that it works:
$ sls invoke local -f hello
{
"statusCode": 200,
"body": "{\"message\":\"Go Serverless v1.0!
Your function executed successfully!\",\"input\":\"\"}"
}
Notice that you can pass an input
when you invoke your Lambda function, either inline or with a .json
or .yml
file
$ sls invoke local -f hello -d "my data"
{
"statusCode": 200,
"body": "{\"message\":\"Go Serverless v1.0!
Your function executed successfully!\",\"input\":\"my data\"}"
}
$ sls invoke local -f hello -p "path_to_my_data_file.yml"
{
"statusCode": 200,
"body": "{\"message\":\"Go Serverless v1.0!
Your function executed successfully!\",\"input\":\"{\"data\":
\"Content of my data file as json\"}\"}"
}
Now you can take a look at the handler.js
file to see that the hello function simply returns a JSON 200 response.
'use strict';
module.exports.hello = (event, context, callback) => {
const response = {
statusCode: 200,
body: JSON.stringify({
message: 'Go Serverless v1.0! Your function executed successfully!',
input: event,
}),
};
callback(null, response);
};
- The
event
variable contains all data from the event that triggered your function. - The
context
variable contains runtime information of the Lambda function that is executing.
We won’t need it here but if you are curious you can check the documentation about the context object on AWS
Code the logic to communicate with your Facebook chat.
You need a webhook (aka web callback or HTTP push API) to first exchange credentials with your Messenger app so that you can start receiving events from it (incoming messages, postback …) and responding to them.
Credentials exchange is done through an HTTP GET event set for your Lambda function.
The HTTP GET event requires an endpoint.
Luckily, Serverless allows you to create one simply by writing a few lines of configuration.
Rename your hello
function to webhook
and add the following config to your serverless.yml
:
...
functions:
webhook: # The name of your lambda function
handler: handler.webhook
events: # All events that will trigger your webhook Lambda function
- http:
path: webook # The path of the endpoint generated with API Gateway
method: GET
integration: Lambda # A method of integration to exchange
# requests and responses between the
# HTTP endpoint and your Lambda function
# The `Lambda` method here works well
# with Messenger's events
Then update your handler.js
file to enable authorisation:
module.exports.webhook = (event, context, callback) => {
if (event.method === 'GET') {
// Facebook app verification
if (
event.query['hub.verify_token'] === 'SECRET_TOKEN'
&& event.query['hub.challenge']
) {
return callback(null, parseInt(event.query['hub.challenge']));
} else {
const response = {
statusCode: 403,
body: JSON.stringify({
message: 'Invalid Token',
input: event,
}),
};
return callback(null, response);
}
} else {
const response = {
statusCode: 400,
body: JSON.stringify({
message: 'Bad Request',
input: event,
}),
};
return callback(null, response);
}
};
- Don’t forget to rename your exported Lambda function
webhook
! - Make sure to choose a strong
SECRET_TOKEN
It is the token that you will have to declare to your Messenger app to enable communication with the chat - The
hub.challenge
is an integer code that Messenger sends you along with the token
Test your handler locally:
$ sls invoke local -f webhook -p -d "{\"method\":\"GET\",\"query\":{\"hub.verify_token\":\"SECRET_TOKEN\",\"hub.challenge\":123456}}"
123456
$ sls invoke local -f webhook -p -d "{\"method\":\"GET\",\"query\":{\"hub.verify_token\":\"BAD_TOKEN\",\"hub.challenge\":123456}}"
{
"statusCode": 403,
"body": "{\"message\":\"Invalid Token\",\"input\":{\"method\":\"GET\",\"query\":{\"hub.verify_token\":\"BAD_TOKEN\",\"hub.challenge\":123456}}}"
}
I recommend you create .yml
or .json
files to invoke your Lambda function locally. It will make your life easier :)
Now that you’ll be able to receive events from Messenger, let’s update your Lambda function to actually handle them.
Add HTTP POST config to your serverless.yml
:
...
functions:
webhook:
handler: handler.webhook
events:
- http:
path: webook
method: GET
integration: Lambda
- http:
path: webook
method: POST
integration: Lambda
To handle the POST requests we will need some more preparation:
-
Create your Messenger app
For that,
- Create an app from your Facebook developer account
- Add the Messenger product to your app
You can access all Facebook products from the left menu “Add a product”
-
Get a page token to be able to post messages on behalf of your page (and have your chatbot respond automatically)
Once you have added Messenger to your app, configure Messenger parameters (accessible from left menu also):
- Under “Token Generation”, select your Facebook page
- Grant access to it with your Facebook account
- Save the token you get for later
-
Add axios to your project to be able to send responses from your bot
$ npm install axios
Now you can edit your `handler.js`:
const axios = require('axios');
const fbPageToken = 'YOUR_FACEBOOK_PAGE_TOKEN';
const fbPageUrl = `https://graph.facebook.com/v2.6/me/messages?access_token=${fbPageToken}`;
module.exports.webhook = (event, context, callback) => {
if (event.method === 'GET') {
// ...
} else if (event.method === 'POST' && event.body.entry) {
event.body.entry.map((entry) => {
// Messenger can send several entry for one event.
// The list contains all the information on the event.
entry.messaging.map((messagingItem) => {
// Each entry can have several messaging data within each event.
// For instance if a user sends several messages at the same time.
// messagingItem contains:
// - the sender information,
// - the recipient information,
// - the message information,
// - other specific information
const senderId = messagingItem.sender.id;
// handle text message
if (messagingItem.message && messagingItem.message.text) {
const payload = {
recipient: {
id: senderId
},
message: {
text: `You say "${messagingItem.message.text}", I say: Hi, let's chat :)`
}
};
axios
.post(fbPageUrl, payload)
.then((response) => {
response = {
statusCode: response.status,
body: JSON.stringify({
message: response.statusText,
input: event,
}),
};
return callback(null, response);
})
.catch((error) => {
const response = {
statusCode: error.response.status,
body: JSON.stringify({
message: error.response.statusText,
input: event,
}),
};
return callback(null, response);
});
}
});
});
} else {
// ...
}
};
You can try to call your Lambda locally, but you won’t be able to get a successful response unless you know a real sender ID.
$ sls invoke local -f webhook -d "{\"method\":\"POST\",\"body\":{\"entry\":[{\"messaging\":[{\"sender\":{\"id\":\"YOUR_SENDER_ID\"},\"message\":{\"text\":\"Hello\"}}]}]}}"
{
"statusCode": 400,
"body": "{\"message\":\"Bad Request\",\"input\":{\"method\":\"POST\",\"body\":{\"entry\":[{\"messaging\":[{\"sender\":{\"id\":\"YOUR_SENDER_ID\"},\"message\":{\"text\":\"Hello\"}}]}]}}}"
}
This means it is time to deploy your project for the first time!
Deploy
As easy as:
$ sls deploy
You’ll see the following logs appear:
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (134.73 KB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................................
Serverless: Stack update finished...
Service Information
service: my-first-chatbot
stage: dev
region: eu-central-1
api keys:
None
endpoints:
GET - https://ENDPOINT_ID.execute-api.eu-central-1.amazonaws.com/dev/webook
POST - https://ENDPOINT_ID.execute-api.eu-central-1.amazonaws.com/dev/webook
functions:
my-first-chatbot-dev-webhook: arn:aws:Lambda:eu-central-1:ID:function:my-first-chatbot-dev-webhook
Congratulations, your webhook is now available from anywhere!
What happened exactly?
Notice that you have a new folder in your project directory:
├── my-first-chatbot
│ ├── .serverless
│ │ ├── cloudformation-template-create-stack.json
│ │ ├── cloudformation-template-update-stack.json
│ │ └── my-first-chatbot.zip
│ ├── node_modules
│ ├── handler.js
│ └── serverless.yml
Let’s examine the first half of the logs to understand:
- Serverless reads your
serverless.yml
file to create two CloudFormation files in the directory.serverless
:- one to create a CloudFormation on AWS through your AWS account
- one to create all the AWS resources you need to have your Lambda function working (here it includes the two API Gateway endpoints you need for your webhook and other credentials settings)
- Serverless packaged all the files in your directory except the
serverless.yml
file, zip it to.serverless/my-first-chatbot.zip
- Serverless then uploads the new files created to an S3 Bucket in the region specified in your
serverless.yml
and creates or update all the resources listed in the CloudFormation update file (including the Lambda function of course)
What you can do now:
-
Invoke your deployed Lambda function
$ sls invoke -f webhook -p -d "{\"method\":\"GET\",\"query\":{\"hub.verify_token\":\"SECRET_TOKEN\",\"hub.challenge\":123456}}" $ sls invoke -f webhook -p -d "{\"method\":\"GET\",\"query\":{\"hub.verify_token\":\"BAD_TOKEN\",\"hub.challenge\":123456}}" $ sls invoke -f webhook -d "{\"method\":\"POST\",\"body\":{\"entry\":[{\"messaging\":[{\"sender\":{\"id\":\"YOUR_SENDER_ID\"},\"message\":{\"text\":\"Hello\"}}]}]}}"
-
Test that your Lambda function is triggered when there is a call to one of the endpoints that were just created
Use curl for instance to query the endpoint https://ENDPOINT\_ID.execute-api.eu-central-1.amazonaws.com/dev/webook
-
Better, test your chatbot live!
Try it! Send your first message to your chatbot
Final settings for your Messenger app:
- Configure Messenger parameters to “Setup Webhooks” under “Webhooks” now that your endpoint is available
- Use the endpoint url as “Callback URL” and your
SECRET_TOKEN
- Subscribe to
messages
and other Messenger events you might want to handle - ”Verify and Save”: Facebook will call the GET endpoint with the token your gave him to subscribe your webhook to the app
- Once done, in the same “Webhook” section, select your Facebook page for subscription: you’ll now listen to the events incoming from this page
Now for the chatbot to send you automatic messages, you need to start the conversation first (otherwise you’ll get a 403)
- If your page is public, find your page in Messenger and send your first message!
- If not, go to the Facebook page and start a conversation from there
Your app is now in prod!
You can start iterating. Facebook allows you to grant permission to testers to use your app before it is validated and available by anyone.
Benchmarking MVP options
Pros and cons
I rated Serverless, EC2, and Heroku based on three criteria:
Serverless
EC2
Heroku
Scalability
++
-
Customization and services
++
-
Ease of use
++
On Heroku,
- You need to configure manually the scale of your infrastructure
- You have fewer integrations than on AWS
- But it is more user-friendly than AWS EC2
- You have less new concepts to understand than Serverless
On the other hand, once you get used to Serverless or EC2s, you can implement your service faster and more easily.
Pricing
I’ll consider two scenarios:
- The custom IFFT: low traffic and light computing memory
- A data processing job running every hour
- Requiring less than 500MB RAM
- Requiring more than 500MB RAM
Serverless
EC2
Heroku
1
0.30€/month
3€/month
Free for 1 app
2.1
0.67€/month
4€/month
7€/month
2.2
1.35€/month
8€/month
25€/month
- Heroku is still a good plan in case 1
- AWS is a better bargain if:
- You need a cron job every hour
- You need lots of computing memory
- Serverless is cheaper than EC2
That’s it for now!
I’ll be happy to have your opinion or feedback if you tried using Serverless or AWS Lambda, or if you have any question or suggestion about this tutorial.
Feel free to leave a comment :)
Sources for the benchmark