Skip to content

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:

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:

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,

Having a Chatbot Running in Prod in 15 Minutes with Serverless

Requirements

Before starting the tutorial, make sure you have:

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);
};

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);
  }
 };

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:

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:

What you can do now:

Try it! Send your first message to your chatbot

Final settings for your Messenger app:

Now for the chatbot to send you automatic messages, you need to start the conversation first (otherwise you’ll get a 403)

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,

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:

  1. The custom IFFT: low traffic and light computing memory
  2. A data processing job running every hour
    1. Requiring less than 500MB RAM
    2. 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

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