AWS CloudFormation Lambda Node.js Twilio Function – Part 2

A

In Part 1, I walked through the process of creating and deploying an AWS Lambda function and API Gateway using AWS CloudFormation. In this post, I will walk though the steps to store POST data to a DynamoDB. The final goal of this project is to allow an AWS Lambda function to receive SMS messages from Twilio. The third post will complete the project from end-to-end when it is written.

Save Post To DynamoDB

The next step in our progression will be to handle requests to the API Gateway and store the response in a DynamoDB. We will be modifying the index.js file and the template.json file again. I am going to start building out the final form of these two files, so I will be removing the HelloWorld function. Below are the updated files.

'use strict';

const AWS = require('aws-sdk'),
    dynamo = new AWS.DynamoDB.DocumentClient(),
    crypto = require('crypto'),
    tableName = process.env.TABLE_NAME;

const createResponse = function(statusCode, body) {
    return {
        statusCode: statusCode,
        body: body
    };
};

// https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
const uuidv4 = function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
};

exports.handleTwilio = function(event, context, callback) {
    let body;
    try {
        body = JSON.parse(event.body);
    } catch(err) {
        callback(null, createResponse(400, 'Unable to Parse Body'));
    }
    if (body) {
        let itm = Object.assign({
            id: uuidv4()
        }, body);

        const params = {
            TableName: tableName,
            Item: itm
        };

        dynamo.put(params).promise()
        .then((data) => {
            // success
            callback(null, createResponse(200, 'Success'));
            return;
        })
        .catch(function(err) {
            // failure
            console.log(err);
            callback(null, createResponse(500, 'Failure'));
        });
    } else {
        callback(null, createResponse(400, 'Bad Request'));
    }
};
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Transform": "AWS::Serverless-2016-10-31",
  "Description": "A simple Lambda function to receive and store SMS messages from Twilio",
  "Resources": {
    "HandleTwilioFunction": {
      "Type": "AWS::Serverless::Function",
      "Properties": {
        "Handler": "index.handleTwilio",
        "Runtime": "nodejs6.10",
        "Environment": {
          "Variables": {
            "TABLE_NAME": {
              "Ref": "TwilioTable"
            }
          }
        },
        "Events": {
          "PostResource": {
            "Type": "Api",
            "Properties": {
              "Path": "/twilio",
              "Method": "post"
            }
          }
        },
        "Role": {
          "Fn::GetAtt": [
            "LambdaRole",
            "Arn"
          ]
        }
      }
    },
    "TwilioTable": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "AttributeDefinitions": [
          {
            "AttributeName": "id",
            "AttributeType": "S"
          }
        ],
        "KeySchema": [
          {
            "AttributeName": "id",
            "KeyType": "HASH"
          }
        ],
        "ProvisionedThroughput": {
          "ReadCapacityUnits": 1,
          "WriteCapacityUnits": 1
        }
      }
    },
    "LambdaRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "Policies": [
          {
            "PolicyName": "TwilioLogDynamoDBReadWrite",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [{
                "Effect": "Allow",
                "Action": [
                  "dynamodb:PutItem",
                  "dynamodb:DeleteItem",
                  "dynamodb:GetItem",
                  "dynamodb:Scan",
                  "dynamodb:Query",
                  "dynamodb:UpdateItem"
                ],
                "Resource": [
                  {
                    "Fn::GetAtt": [
                      "TwilioTable",
                      "Arn"
                    ]
                  }
                ]
              }]
            }
          }
        ],
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs",
          "arn:aws:iam::aws:policy/AWSLambdaExecute"
        ],
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": "lambda.amazonaws.com"
              },
              "Action": "sts:AssumeRole"
            },
            {
              "Effect": "Allow",
              "Principal": {
                "Service": "apigateway.amazonaws.com"
              },
              "Action": "sts:AssumeRole"
            }
          ]
        }
      }
    }
  }
}

I know this looks like a lot, but it really is not that much. First, in the index.js file, the handleTwilio function is added to receive content from the API Gateway. By default, API Gateway serializes the POST Body and submits it as event.body. I am simple parsing event.body and using Dynamo DocumentClient to store the values to the TABLE_NAME environment variable. In template.json, three resources are created: TwilioTable, LambdaRole, and HandleTwilioFunction. TwilioTable describes the DynamoDB table and has the primary key, id. LambdaRole is the role that that the lambda function will use when it executes. The role has basic read/write access to the TwilioTable and this role can be assumed by API Gateway and Lambda. For more information on this please see the IAM CloudFormation Documentation. Lastly, the HandleTwilioFunction describes the the Environment Variable TABLE_NAME as a reference to our new TwilioTable.

Once again, package and deploy this code.

aws cloudformation package \
    --template-file template.json \
    --s3-bucket S3_Bucket_Name \
    --output-template-file packaged-template.json \
    --use-json

aws cloudformation deploy \
    --template-file packaged-template.json \
    --stack-name your-stackname-here \
    --region us-west-2 \
    --capabilities CAPABILITY_IAM

Once this is packaged and deployed, you should be able to test this functionality on the API Gateway Console page with a sample POST body like:

{
 "name": "Mark Slosarek",
 "title": "Web Developer"
}

This will create a new record in the DynamoDB table created by the CloudFormation template.

In Part 3, I walk though the steps accept and respond to Twilio messages.

About the author

Add comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

By mark

Recent Posts

Categories