Serverless Rest API using nodejs and AWS Lambda

Author
April 06, 2022

In this article i will explain step by step how you can create REST Api using nodejs and AWS Lambda

Step 1: REST API definition

Create a .json file to define all the rest api you need to add on your aws api gateway

{
  "openapi": "3.0.0",
  "info": {
    "title": "Notes API",
    "description": "This API supports the creation and retrieval of a Notes Object.",
    "contact": {
      "email": "singhanku658@gmail.com"
    },
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "",
      "description": "SwaggerHub API Auto Mocking"
    }
  ],
  "tags": [
    {
      "name": "admins",
      "description": "Secured Admin-only calls"
    },
    {
      "name": "developers",
      "description": "Operations available to regular developers"
    }
  ],
  "paths": {
    "/pet": {
      "get": {
        "tags": ["developers"],
        "summary": "Find note by ID",
        "description": "Returns the matching note object",
        "operationId": "findNote",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "description": "ID of the note to return",
            "required": true,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Note object matching criteria",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "400": {
            "description": "Invalid ID supplied"
          },
          "404": {
            "description": "Note not found"
          }
        }
      },
      "post": {
        "tags": ["developers"],
        "summary": "Adds a note with form data",
        "description": "Adds a note object to the system",
        "operationId": "addNote",
        "requestBody": {
          "description": "Note to add",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Note"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Note object created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "400": {
            "description": "invalid input, object invalid"
          },
          "409": {
            "description": "an existing note already exists"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Note": {
        "required": ["name", "description", "createdOn"],
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "My Note"
          },
          "description": {
            "type": "string",
            "example": "My Description"
          },
          "createdOn": {
            "type": "string",
            "format": "date",
            "example": "2012-05-15"
          }
        }
      }
    }
  }
}

Step 2: Create Cloudformation template for setting up our serverless infrastructure

  • Login to your aws account and got to cloud formation and click on create stack-> With existing resources(imported resources)

create-stack

  • Selecting the template file and click Next

  • Name the new stack NoteAPI or something similar and then click Next

  • Keep all the default options on the Configure stack options page and click Next

  • At the bottom of the Review page, check the option to allow CloudFormation to create an IAM role.

  • Copy paste below snippet to and save it in a .json file and use it to create lambda function and dynamodb

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "AWS CloudFormation Template To Create a DynamoDB with Lambda Role",
  "Parameters": {
    "HashKeyElementName": {
      "Type": "String",
      "Default": "id",
      "Description": "Hash Key Name"
    },
    "HashKeyElementType": {
      "Type": "String",
      "Default": "S",
      "Description": "Hash Key Type"
    }
  },
  "Resources": {
    "DynamoDBTable": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "TableName": "Notes",
        "AttributeDefinitions": [
          {
            "AttributeName": {
              "Ref": "HashKeyElementName"
            },
            "AttributeType": {
              "Ref": "HashKeyElementType"
            }
          }
        ],
        "KeySchema": [
          {
            "AttributeName": {
              "Ref": "HashKeyElementName"
            },
            "KeyType": "HASH"
          }
        ],
        "ProvisionedThroughput": {
          "ReadCapacityUnits": 1,
          "WriteCapacityUnits": 1
        }
      }
    },
    "FunctionSet": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "FunctionName": "NoteLambda-Set",
        "Handler": "index.lambda_handler",
        "Runtime": "nodejs16.x",
        "Code": {
          "ZipFile": "import json\ndef handler(event, context) :\n  print(\"Event: %s\" % json.dumps(event))\n"
        },
        "Role": {
          "Fn::GetAtt": ["LambdaExecutionRole", "Arn"]
        },
        "Timeout": "2"
      },
      "DependsOn": ["LambdaExecutionRole"]
    },
    "FunctionGet": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "FunctionName": "NoteLambda-Get",
        "Handler": "index.lambda_handler",
        "Runtime": "nodejs16.x",
        "Code": {
          "ZipFile": "import json\ndef handler(event, context) :\n  print(\"Event: %s\" % json.dumps(event))\n"
        },
        "Role": {
          "Fn::GetAtt": ["LambdaExecutionRole", "Arn"]
        },
        "Timeout": "2"
      },
      "DependsOn": ["LambdaExecutionRole"]
    },
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "Policies": [
          {
            "PolicyName": "LambdaPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                  ],
                  "Resource": ["arn:aws:logs:*:*:*"],
                  "Effect": "Allow"
                },
                {
                  "Action": ["dynamodb:PutItem", "dynamodb:GetItem"],
                  "Resource": {
                    "Fn::GetAtt": ["DynamoDBTable", "Arn"]
                  },
                  "Effect": "Allow"
                }
              ]
            }
          }
        ],
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": ["sts:AssumeRole"],
              "Effect": "Allow",
              "Principal": {
                "Service": ["lambda.amazonaws.com"]
              }
            }
          ]
        }
      }
    }
  }
}
  • Once you complete the above step it will create a dynamoDB table named Notes with primary key named id

  • Create An IAM Role that grants a Lambda function with permission to write and read to dynamodb table

  • Create two lambda function one for get and one for post

Step 3: Writing code to lambda function

  • On the AWS Console navigate to lambda home page and there would be two function created using our cloudformation template above

  • We will select NoteLambda-Set function and write code to insert data to dynamodb, open index.py file and copy paste below code snippet

var AWS = require("aws-sdk");
exports.lambda_handler = async (event) => {
  try {
    var obj = JSON.parse(event.body);
    var params = {
      TableName: "Notes",
      Item: {
        id: { S: obj.id },
        name: { S: obj.name },
        description: { S: obj.description },
        createdOn: { S: obj.createdOn },
      },
    };
    const data = await ddb.putItem(params).promise();
    var response = {
      statusCode: 200,
      body: JSON.stringify({
        message: "Data entered successfully",
      }),
    };
    console.log("Item entered successfully:", data);
  } catch (err) {
    console.log("Error: ", err);
    return err;
  }
  return response;
};
  • Now update the NoteLambda-Set function open index.py file and copy paste the following code snippet
var AWS = require("aws-sdk");
exports.lambda_handler = async (event) => {
  try {
    var obj = JSON.parse(event.body);
    var ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });
    const { id } = event.pathParameters;
    const params = {
      TableName: "Notes",
      Key: {
        id: id,
      },
    };
    const data = await ddb.getItem(params).promise();
    var response = {
      statusCode: 200,
      body: JSON.stringify({
        data: data
        message: "Data entered successfully",
      }),
    };
  } catch (err) {
    console.log("Error: ", err);
    return err;
  }
  return response;
};

Step 4: Create API Gateway

Navigate to API Gateway in your aws console and do the following steps

  • Click the Import button and select the Import from Swagger or Open API 3 option and then select the .json file created in Step 1 above
  • Once done importing you will see Get and Post API and now we will setup Get and POST, Click on setup post endpoint
  • Set the integration type to Lambda function region will be same as lambda function
  • Type the name of the lambda function in the lambda function field and click save

Configuring the GET endpoint

  • Click Set up now for the GET endpoint.
  • Set the Integration type to Lambda Function. The region is the same one where you defined your functions.
  • Type NoteLambda-Get into the Lambda Function field and select Save.
  • AWS will prompt you again to add permissions for the API Gateway to call your function, so click OK.
  • Select the Method Request box
  • Under URL Query String Parameters add a query string named id, and mark it as required.
  • Click the checkmark to save it.
  • Select Method Execution to return to the main configuration page for the GET endpoint.
  • Select the Integration Request box.
  • Under Mapping Templates, select When there are no templates defined (recommended).
  • Select the option to Add mapping template.
  • Set the Content-Type to application/json and select the checkmark to save the value.
  • In the Generate Template box, paste the following:
{
  "id": "$input.params('id')"
}
  • This configuration will map the query string parameter to an input parameter for the Lambda. Click Save when you’re done.

Step 5: Deploy and test API gateway

  • From the main screen for your API, click on Actions, and choose Deploy API.
  • Choose [New Stage] from the Deployment stage dropdown, and then enter a name and click deploy
  • Once deployed you will find custom URL at the top of the page copy the url and paste in the browser and just add /note?id=d290f1ee-6c54-4b01-90e6-d701748f0851