Tidbit: Getting started with AWS Lambda function using nodeJS

This is a Tidbit, basically whenever you see Tidbit: in the title,
it means I am going to simply throw some helpful code without
explaining too much around it.

What will you be able to do after going through this?

Build & deploy a REST API on AWS Lambda using nodeJS which uses AWS API gateway as its front and AWS Dynamo DB as its storage. We will deploy the Lambda function using the Serverless framework, if you don’t know what this is, I recommend you familiar with it using this article of mine to get started with Serverless framework on AWS.

What will it look like?

enter image description here

The API will have two Lambda functions running, one for creating an entity called Domain and the other for retrieving it.

The code

  1. Create a new directory for your project
  2. Run npm init #follow the guide to create package.json
  3. npm install serverless -g installs the serverless framework globally.
  4. serverless create --template aws-nodejs --path domain Creates a directory called domain and creates a serverless.yml file in it with default values for a nodeJS project.
  5. Delete the default handler.js file created and instead create two new files called get.js and put.js in the domain directory.

    get.js

'use strict';

var AWS = require('aws-sdk'),
    uuid = require('uuid'),
  dynamoClient = new AWS.DynamoDB.DocumentClient(); 

module.exports.domainget = (event, context, callback) => {
  var params = {
        Key: {
            "Id": event.queryStringParameters.id,
        },
        TableName : process.env.dynamoTableName
    };
    dynamoClient.get(params, function(err, data){
    var response;
        if (err){
      response = getErrorResponse(event, err);
    } else{
      response = getSuccessResponse(data);
    }

    callback(null, response);
  });
};

var getSuccessResponse = function (data){
  console.log("Retrieved domain with id: " + data.id)
  var response = {
    statusCode: 200,
    body: JSON.stringify(data)
  };

  return response;
}

var getErrorResponse = function (input, err){
  console.log({
    input: input,
    err: err
  });
  var response = {
    statusCode: 500,
    body: JSON.stringify({
      message: err,
      input: input
    })
  };

  return response;
}

The request and response follows the AWS API gateway expectations for Lambda response expectations.

The Lambda function stops executions after you call the callback function passed to the JS function via the Lambda runtime.

The rest of the code should be self-explanatory.

put.js

'use strict';

var AWS = require('aws-sdk'),
    uuid = require('uuid'),
  dynamoClient = new AWS.DynamoDB.DocumentClient(); 

module.exports.domainput = (event, context, callback) => {
  var id = uuid.v1();
  var body = JSON.parse(event.body);
  var params = {
        Item : {
            "Id": id,
      "Name": body.name,
      "Desc": body.desc
        },
        TableName : process.env.dynamoTableName
    };
    dynamoClient.put(params, function(err, data){
    var response;
        if (err){
      response = getErrorResponse(event, err);
    } else{
      response = getSuccessResponse(id, event);
    }

    callback(null, response);
  });
};

var getSuccessResponse = function (id, input){
  console.log("Created domain with id: " + id)
  var response = {
    statusCode: 200,
    body: JSON.stringify({
      location: "/domains/get/" + id,
      input: input
    })
  };

  return response;
}

var getErrorResponse = function (input, err){
  console.log({
    input: input,
    err: err
  });
  var response = {
    statusCode: 500,
    body: JSON.stringify({
      message: err,
      input: input
    })
  };

  return response;
}

serverless.yml

service: domains

provider:
  name: aws
  runtime: nodejs6.10
  region: us-east-1

  environment:
    dynamoTableName: ${opt:stage}-domains
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.dynamoTableName}"

package:
  include:
    - get.js
    - put.js
#  exclude:
#    - exclude-me.js
#    - exclude-me-dir/**

functions:
  domainget:
    handler: get.domainget
    events:
      - http:
          path: domains
          method: get
  domainput:
    handler: put.domainput
    events:
      - http:
          path: domains
          method: put

resources:
  Resources:
    usersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${opt:stage}-domains
        AttributeDefinitions:
          - AttributeName: Id
            AttributeType: S
        KeySchema:
          - AttributeName: Id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

Deploy the code

You should have your AWS credentials configured on the machine with enough access to create IAM roles, S3 bucket (for Lambda code upload and serverless bucket creation), ability to create Cloudformation scripts, API gateway creation, Lambda creation & Dynamo DB creation.

Everything this creates will lie in the AWS free tier if you qualify for it, remember, you are responsible for your costs though.

Run the following commands:

cd domain

#will create the get & put lambda functions, api gateway & dynamo db table to store domains
serverless deploy --region us-east-1 --stage dev

Try the service

Note the API gateway endpoint created in the deploy step above, it should be present in the output. In case you don’t have the output anymore, go to AWS console and get the API URL from the API Gateway part of the console.

Example:

Create a domain

Request:

PUT https://2w31hwi1f6.execute-api.us-east-1.amazonaws.com/dev/domains HTTP/1.1
User-Agent: Fiddler
Host: 2w31hwi1f6.execute-api.us-east-1.amazonaws.com
Content-Length: 57

{
  "name": "a domain",
  "desc": "some description"
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1571
Connection: keep-alive
Date: Mon, 26 Mar 2018 09:10:22 GMT
x-amzn-RequestId: 839271de-30d5-11e8-99e5-75b6d1ec532c
x-amz-apigw-id: EWHX0Fq3IAMFSHg=
X-Amzn-Trace-Id: sampled=0;root=1-5ab8b8fe-29ea1c26fe1c5797c9d33d25
X-Cache: Miss from cloudfront
Via: 1.1 622984f77dccd480b1b655dc4625dd24.cloudfront.net (CloudFront)
X-Amz-Cf-Id: -YALvHLfr2WlmRHcHjakoQiApkVSO_jilEoXZYzjjpz64t45SqcwSQ==

{"location":"/domains/get/83990110-30d5-11e8-b030-3585da8b8c33","input":{"resource":"/domains","path":"/domains","httpMethod":"PUT","headers":{"CloudFront-Forwarded-Proto":"https","CloudFront-Is-Desktop-Viewer":"true","CloudFront-Is-Mobile-Viewer":"false","CloudFront-Is-SmartTV-Viewer":"false","CloudFront-Is-Tablet-Viewer":"false","CloudFront-Viewer-Country":"IN","Host":"2w31hwi1f6.execute-api.us-east-1.amazonaws.com","User-Agent":"Fiddler","Via":"1.1 622984f77dccd480b1b655dc4625dd24.cloudfront.net (CloudFront)","X-Amz-Cf-Id":"tH9mAyUJ7zUwvZ8M0UdjNe-DeKeRt7FQC4bRfGVZ5vWPYv-MKcCpNA==","X-Amzn-Trace-Id":"Root=1-5ab8b8fe-8604b2b42c3fbe7678040f00","X-Forwarded-For":"114.143.135.82, 54.182.245.48","X-Forwarded-Port":"443","X-Forwarded-Proto":"https"},"queryStringParameters":null,"pathParameters":null,"stageVariables":null,"requestContext":{"resourceId":"wrhq1m","resourcePath":"/domains/put","httpMethod":"PUT","extendedRequestId":"EWHX0Fq3IAMFSHg=","requestTime":"26/Mar/2018:09:10:22 +0000","path":"/dev/domains/put","accountId":"522390200088","protocol":"HTTP/1.1","stage":"dev","requestTimeEpoch":1522055422838,"requestId":"839271de-30d5-11e8-99e5-75b6d1ec532c","identity":{"cognitoIdentityPoolId":null,"accountId":null,"cognitoIdentityId":null,"caller":null,"sourceIp":"114.143.135.82","accessKey":null,"cognitoAuthenticationType":null,"cognitoAuthenticationProvider":null,"userArn":null,"userAgent":"Fiddler","user":null},"apiId":"2w31hwi1f6"},"body":"{\r\n  \"name\": \"a domain\",\r\n  \"desc\": \"some description\"\r\n}","isBase64Encoded":false}}

Get the created domain:

Request:

GET https://2w31hwi1f6.execute-api.us-east-1.amazonaws.com/dev/domains/?id=83990110-30d5-11e8-b030-3585da8b8c33 HTTP/1.1
User-Agent: Fiddler
Host: 2w31hwi1f6.execute-api.us-east-1.amazonaws.com
Content-Length: 0

Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 98
Connection: keep-alive
Date: Mon, 26 Mar 2018 09:13:11 GMT
x-amzn-RequestId: e80db607-30d5-11e8-8d33-a506aa878829
x-amz-apigw-id: EWHyKF1zIAMFQeQ=
X-Amzn-Trace-Id: sampled=0;root=1-5ab8b9a7-2dcc0c49e6716d5701fe8cea
X-Cache: Miss from cloudfront
Via: 1.1 622984f77dccd480b1b655dc4625dd24.cloudfront.net (CloudFront)
X-Amz-Cf-Id: 3uxvpW74GzyJG4BGgUCrgyh1pc_lEobzl5zfAjku2kPS7PWYX24Ftw==

{"Item":{"Desc":"some description","Id":"83990110-30d5-11e8-b030-3585da8b8c33","Name":"a domain"}}

Remove deployment

Run the following:

# assumption: start from root folder
cd domain

serverless remove --region us-east-1 --stage dev

That’s it.

If you liked this article, you can choose to follow this blog/subscribe to email alerts (floating follow button {bottom-right} or below comments in mobile) so that you know when any future posts come about.

Advertisement

3 thoughts on “Tidbit: Getting started with AWS Lambda function using nodeJS

  1. Pingback: 1 – Getting started with AWS Lambda function using nodeJS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s