DEV Community

Harsh
Harsh

Posted on

Building Serverless REST APIs on AWS: A Step-by-Step Guide with Lambda, API Gateway, and DynamoDB

Hello Devs,

Today we are going to see how we can use AWS serverless infrastructure to create RESTAPIs. This is just basic demo, we cant use it in project but can be used for mini projects. Also this is in free tier ๐ŸคŸ๐Ÿป

lets start with basic intro of all the service we are going to use.

1. AWS lambda function

  • AWS Lambda is a serverless computing service provided by Amazon Web Services (AWS). Serverless computing allows you to run code without provisioning or managing servers. With AWS Lambda, you can execute your code in response to specific events, such as changes to data in an Amazon S3 bucket, updates to a DynamoDB table, or an HTTP request via API Gateway.

More information : here.

2. API Gateway

  • Amazon API Gateway is a fully managed service provided by Amazon Web Services (AWS) that allows you to create, deploy, and manage APIs (Application Programming Interfaces) at scale. It acts as a gateway between your applications and the backend services, enabling you to create RESTful or WebSocket APIs.

More information : here

3. DynamoDB

  • Amazon DynamoDB is a fully managed NoSQL database service provided by Amazon Web Services (AWS). It is designed to provide fast and predictable performance with seamless scalability. DynamoDB is suitable for a variety of use cases, ranging from simple key-value stores to more complex applications with high read and write throughput requirements.

More information : here

Now lets start by doing ๐Ÿคž๐Ÿผ

Flow

STEPS

  1. Create a Permission Policy
  2. Create Execution Role for Lambda function
  3. Create Function
  4. Create RESTAPI using API Gateway
  5. Create Resources on Your RESTAPI
  6. Create a DynamoDB table
  7. Write and understand Code
  8. Test the integration of API Gateway, Lambda, and DynamoDB.

Optional
9.Deploy (not on your domain because idk how to yet๐Ÿ˜‰)

1. Create a Permission Policy

  • Go to Your AWS console and search IAM

search iam

  • Click on Polices

policies

  • Create New Policy
  • Select JSON and paste the following code
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Action": [
        "dynamodb:DeleteItem",
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:Query",
        "dynamodb:Scan",
        "dynamodb:UpdateItem"
      ],
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Sid": "",
      "Resource": "*",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Effect": "Allow"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
  • So what does it says, it says to whichever service it is attached to will have access to DynamoDB and to create Logs.
  • Click next and give name to it, I am giving lambda-apigateway-policy
  • You can also add tags if you can.

2. Create Execution Role for Lambda function

  • Click on roles Roles
  • Click on create new role
  • Select AWS service and and Service use case as lambda and click next

attach policy

  • Click next and search the Policy we created earlier

search poilcy

  • Click next and name the role with any name as per your ease of understanding, i am giving lambda-apigateway-role.

3. Create Function

  • Search lambda
  • Click Functions and create.

Click Functions and create one

  • Give name of function I am naming LambdaFunctionOverHttps
  • Runtime environment as Node.js 18.x
  • Select role we created earlier

selecting role

  • Leave everything default and create lambda function

  • Will create the function at last because will try to understand it little.

4. Create RESTAPI using API Gateway

  • Search *API gateway *
  • In APIs section create new API with RESTAPI(Not private one)
  • Select new API and name it I am naming it DynamoDBOperations
  • Leave everything default

Search API gateway

5. Create Resources on Your RESTAPI

Click on create resources

  • Name it, i am naming getallusers.

Name it i am giving getallusers

  • Click on getallusers, make sure its highlighted then click create method.

Click on getallusers

  • In create method select the method type as GET.
  • Also make sure you select lambda function we created earlier and enable lambda proxy integration.
  • With Lambda Proxy Integration, the Lambda function receives the entire request object from the API Gateway, including information such as headers, query parameters, path parameters, and the request body. The Lambda function then processes the request and returns a response object, which includes the status code, headers, and the response body.

 method select

  • Now we will create another endpoint or say resources with four method GET POST PUT DELETE, i am naming as user make sure you have lambda proxy integration enabled, it will look something like this.

endpoints

6. Create a DynamoDB table

  • Search DynamoDB.
  • Click on Tables.
  • Create a Table with whichever name you want I am naming Users
  • Set Partition key as id with type String.
  • Leave everything default and click on create table.

Create Table

7. Write and understand Code

  • You can find code here : repo

  • lets start with installing node modules and AWS SDK(Software development kit)

  • We need following modules

    1. aws-sdk/lib-dynamodb. More Details here and here
    2. aws-sdk/client-dynamodb. More Details here and here.

Lets start understanding code

  • First we Import what we need
import { DynamoDBDocumentClient, PutCommand, GetCommand, UpdateCommand, DeleteCommand, ScanCommand } from "@aws-sdk/lib-dynamodb";

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";

const region = "us-east-1";
const ddbClient = new DynamoDBClient({ region: region });
const ddbDocClient = DynamoDBDocumentClient.from(ddbClient);
const tablename = "Users";
Enter fullscreen mode Exit fullscreen mode
  • First we create ddbClient , DynamoDBClient with specified AWS region. This client will be used to interact with DynamoDB
  • The DynamoDBDocumentClient is a higher-level client provided by the AWS SDK for JavaScript. It simplifies working with DynamoDB by providing a more JavaScript-friendly API, and it is built on top of the lower-level DynamoDBClient. It is created from the previously created DynamoDBClient instance.
  • Please add your region in which you have created all the resources. And also tablename.
export const handler = async (event) => {
..
..
..
}
Enter fullscreen mode Exit fullscreen mode
  • This Block is provided by lambda function in which we can respond to specific event.
  • Event is an object which is returned by lambda function if we enable lambda proxy during we created method in API resource creation section.
  • If you want to see what that contains then just return event in response, will see later.

  • Here we have specified two paths getalluserPath and userpath as per our endpoints.

  • We call a specific javascript function based on httpMethod and endpoint path.

  • We get details of both from the event object.

  • You should once return this event object as a response so that whole concept will be much clearer.

  • Make sure you

export const handler = async (event) => {
  let response;

  const getallusersPath = "/getallusers";
  const userpath = "/user";
  const body = JSON.parse(event.body);
  // event.payload.TableName = tablename;
  switch (true) {
    case event.httpMethod === "GET" && event.path === getallusersPath:
      response = getallusers();
      break;
    case event.httpMethod === "GET" && event.path === userpath:
      response = getSingleuser(event.queryStringParameters.id);
      // response = buildResponse(200, "hello there");
      break;
    case event.httpMethod === "POST" && event.path === userpath:
      // response = buildResponse(200 , event.queryStringParameters.id);
      response = saveUser(body);
      break;
    case event.httpMethod === "PUT" && event.path === userpath:
      response = updateUser(body);
      break;
    case event.httpMethod === "DELETE" && event.path === userpath:
      // response = buildResponse(200,bod1.payload);
      response = deleteUser(body.payload);
      // response = buildResponse(200, "hello there");
      break;
    default:
      response = buildResponse(404, "404 Not Found");
  }
  return response;
};
Enter fullscreen mode Exit fullscreen mode
  • I will explain one function and rest is same.
  • What we are doing here is creating an object of the required method and then sending that object to client, we created earlier so that it can execute our query.
  • lets take example of saveUser function which saves the user in the Dynamodb.
  • Here i have specific structure of request body.
  • The id is user specified right now but it should be Dynamically created Sorry for that ๐Ÿ˜ถ
{
    "payload" : {
        "Item": {
            "id": "1255",
            "username":"Bruce Wayne ",
            "description":"I love Vadapav :)"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Anything we add under the Item section will be added to database
  • In PutCommand object below we have three keys one is specifying TableName, Item to be added, and a ConditionExpression in which it is define a condition that if id is already present than dont add it and return 400 error code.
  • There are plenty of such conditions DynamoDB has.

More Information : here

async function saveUser(body) {
  if (!body.payload) {
    return buildResponse(400, "payload not found");
  } else {
    const command = new PutCommand({
      TableName: tablename,
      Item: body.payload.Item,
      ConditionExpression: "attribute_not_exists(id)",
    });

    try {
      let response = await ddbDocClient.send(command);
      return buildResponse(200, response);
    } catch (error) {
      return buildResponse(500, error);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Each command object has a specific format to be followed majority times.
  • The update command will be somewhat difficult to understand because as we have NoSQL database, i made a dynamic query means as we have no specific schema any record can have different attributes so this will automatically handle any number of update attributes or say columns
const updateCommand = new UpdateCommand({
       TableName: "YourTableName",
       Key: {
         id: "1234ABCD", // Assuming "id" is the primary key
      },
      UpdateExpression: "SET firstName = :newFirstName, lastName = :newLastName, age = :newAge",
       ExpressionAttributeValues: {
         ":newFirstName": "John",
         ":newLastName": "Doe",
         ":newAge": 30,
       },
       ReturnValues: "ALL_NEW",
     });
Enter fullscreen mode Exit fullscreen mode
  • UpdateExpression contains all the attribute one want to update.
  • ExpressionAttribute contains the value of attribute to change;
  • ReturnValues return the response of newly updated record.
  • If you want to understand more in detail go through document or videos but please make sure you watch latest video because AWS changes many things and you will get errors. I suggest documentation much more.
  • This is all now we will test it

8. Test the integration of API Gateway, Lambda, and DynamoDB

  • Copy the code and paste it in index.mjs of lambda function.

code upload

  • If you getting error because of node module then upload the zip file containing all the files.

  • *Go to APIGateway and click on the resource method of specific endpoint let us start with getalluser.
    *

  • Make sure of your resource path.

  • Mine is somewhat different because i nested all above paths under DynamoDBManger.

  • so my path is /DynamoDBManager/getallusers(yours should be /getallusers).

  • And my index.mjs file also has same path as above so make sure to update as per your resource.
    path

  • Click on Test section

getalluser

  • Click on Test and you will get response along with the some logs
  • You wont get any response as your table doesn't have any data

response

  • Now lets create some records
  • Click on POST of /user resource and go to test section
  • In request body paste following JSON
  • Here id is mandatory and attributes can be anything.
{
    "payload" : {
        "Item": {
            "id": "1255",
            "username":"MS dhoni",
            "description":"Best of all time"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

post response

  • You will get following response if id already exists
  • Here you can see we have ConditionalCheckFailedException because we had condition mentioned in our code that id should not exist in table

condition

- GET user with id we created

  • For GET request we are using Query strings with id to our table id

Get user

  • Will get following response if id is not available.

id

- UPDATE the user with id we created

  • For update use the following structure or JSON
{
    "payload" : {
       "id":"1200",
       "attributes" : {
           "username":"Sachin Tendulkar"
       }
 }
}
Enter fullscreen mode Exit fullscreen mode
  • Here attribute name should be same as the original one and the value you wanna update can be different.

put response

  • You can explore what errors you can have.

9.Deploy

  • Go to APIgateway and select your API and than click deploy.
  • Select NEW stage, name it dev or anything.
  • Now select the method and you will get the link

deploy

  • You can use postman on this links
  • By deploying you will get link which can be accessed by public.
  • I recommend don't share it yet.

๐Ÿฐ Conclusion ๐Ÿง

  • If you reached here than thank you for reading my naive blog.
  • It took me 2 days to create this because I used old AWS-SDK on node20 runtime environment๐Ÿ˜‚ and got many errors.
  • Also I used same lambda function for all APIs which not good at all.
  • Again I exposed ID to user which is not good, so I did some wrong practice which is not right and will update this in next project in which will try to integrate the AWS Cognito along with this.
  • There is auto-incrementing attribute for id in DynamoDB you can modify and add it to your code as a part of exploration
  • Avoid Hardcoding some variable like DB name, AWS-region instead consider using environment variables or AWS Systems Manager Parameter Store to manage such configurations.
  • This is overview project to get familiar with AWS services.
  • If I did something wrong tell me so can avoid it later.
  • console.log("See you later ๐Ÿ˜‰");

Top comments (2)

Collapse
 
onlinemsr profile image
Raja MSR

The post is well-structured, easy to follow, and provides useful code snippets. I appreciate the you effort in creating this guide, which is a valuable resource for anyone looking to build a serverless REST API on AWS.

Collapse
 
harsh_gajjar profile image
Harsh

Thank you, stay tuned for next incorporation of AWS Cognito and secret manager