Create Serverless REST APIs using - AWS Lambda, DynamoDB, API Gateway, JavaScript

Amir Mustafa
AWS in Plain English
10 min readDec 19, 2021

--

c

→ In this article, we create an AWS serverless REST API using Lambda, DynamoDB, API Gateway (three AWS services), and JavaScript

→ This article aims to insert, read, update and delete products from Dynamo DB using the lambda function. Client will interact

DynamoDB: AWS database

Lambda: FaaS which will push data in Dynamo DB

API Gateway: Client (Postman) interacts with Lambda with the help of this AWS service

Let us set up three services for the application

  1. Dynamo DB:

→ This is a serverless database. We will create a product table that will store data

→ Go to the AWS console and search DynamoDB. Click Create table button

→ Write the table name and primary key

i.e. product-inventory and productId respectively

→ Click Create table button

→ Our table is ready

2. Lambda Function:

→ We will create an empty Lambda function in Node.js technology with a new role.

→ We will attach two policies — Cloudwatchlogs and DynamoDB full access to it

→ The logic of the Lambda function will be written from a JavaScript file from local code-editor

→ Open Lambda service from AWS and click Create function

→ Chose first tab (i.e. author from scratch). Write your Lambda function name and runtime will be Node.js

eg. serverless-api

→ Scroll more down → Click change default execution → Create a new role → Create function

→ Lambda function with the fresh role is now created. Now we need to attach policies.

→ Click on configurations → Permissions and click the newly created role i.e. server-api-role

→ Click attach role — Add two policies i.e. Cloudwatch full access and DynamoDB full access

→ Click Attach Policy

3. API Gateway:

→ JavaScript/Lambda code cannot interact with the →dynamo database directly. For this, we need an AWS service.

→ API Gateway creates a REST environment, where JavaScript code can connect and execute the Lambda functions.

→ API Gateway will allow us to create resources. By resource, we mean API endpoints eg. /products. On top of resources we write different methods eg. GET

→ Each method in the resource will give us was URL which we will execute from Postman.

→ Let us now create endpoints for Postman. Search and open API Gateway and click Get Started

→ The latest AWS interface provides four types of screen API. We chose HTTP API Build

→ Give the API Name eg. serverless-api

→ On the next page, we chose Lambda Integrations and chose the recently created Lambda function i.e. serverless-api

→ Click Create API. It will take you to the empty resource page of the newly created API (i.e. serverless-api)

→ Now here we will create resources (i.e. API endpoints) followed by creating methods on each resource. We will see how each method in the resource will configure with a lambda function.

→ Click Actions → Create Resource

→ Enter your resource endpoint

eg. /heath (for checking the health of the API). Click Create Resource

→ Select your resource → Click Actions → Create Method

→ Chose method type. Each method will be triggered from postman and lambda function

eg. GET type, here we will configure with Lambda function. We will later invoke this method using the Lambda function

→ We make sure to choose:

Integration type: Lambda Function,

Check Use Lambda Proxy Integration, select our nearest AWS region and choose our recently created lambda function (i.e. Lambda function).

Click the Save button.

→ We have created one resource /health, (GET). For our application, we require multiple endpoints. All will be configured with Lambda, where we will write the logic for products CRUD.

a. /products:

GET: Gets all products

b. /product:

i. GET: Gets the single product

ii. POST: Add a new product

ii. PATCH — Update a new product

iv. DELETE: Deletion of a product:

→ Click on Action → Deploy → It asks for stage → Chose to prod → Deploy

→ After deployment in Stage tab, prod, every API method will provide one endpoint. This we use in URL of the postman to invoke after Lambda function will be complete

→ So finally endpoints are ready which are configured with Lambda function. Now we will write Lambda code, where we will invoke each endpoint.

4. JavaScript:

→ For this, we will use the AWS-SDK package. At the end where our code is completed, we will copy-paste code in AWS Lambda console which we have created in point 2

For installing AWS

 npm install aws-sdk

→ Let us write code

→ So we will read, add, update, delete products from Dynamo DB.

→ Basic setup:

index.js

const AWS = require("aws-sdk");const AWS_REGION = "ap-south-1";AWS.config.update({
region: AWS_REGION,
});
const dynamoDB = new AWS.DynamoDB.DocumentClient();const dynamoDBTableName = "product-inventory";// Resources(endpoints) created in API Gatewayconst healthPath = "/health";
const productPath = "/product";
const productsPath = "/products";
exports.handler = async function (event) {
console.log("Request event" + event);
let response;
switch (true) {
case event.httpMethod === "GET" && event.path === healthPath:
response = buildResponse(200);
break;
case event.httpMethod === "GET" && event.path === productPath:
response = await getProduct(event.queryStringParameters.productId);
break;
case event.httpMethod === "GET" && event.path === productsPath:
response = await getProducts();
break;
case event.httpMethod === "POST" && event.path === productPath:
response = await saveProduct(JSON.parse(event.body));
break;
case event.httpMethod === "PATCH" && event.path === productPath:
const requestBody = JSON.parse(event.body);
response = await modifyProduct(
requestBody.productId,
requestBody.updateKey,
requestBody.updateValue
);
break;
case event.httpMethod === "DELETE" && event.path === productPath:
response = await deleteProduct(JSON.parse(event.body).productId);
break;
default:
response = buildResponse(404, "404 Not Found");
}
return response;};// Get Specific Product
async function getProduct(productId) {
const params = {
TableName: dynamoDBTableName,
Key: {
productId: productId,
},
};
return await dynamoDB
.get(params)
.promise()
.then((response) => {
return buildResponse(200, response.Item);
},
(err) => console.log("ERROR: ", err)
);
}
// Gets all products
async function getProducts() {
const params = { TableName: dynamoDBTableName };const allProducts = await scanDynamoRecords(params, []);const body = {products: allProducts,};return buildResponse(200, body);}async function scanDynamoRecords(scanParams, itemArray) { try {
// Read Dynamo DB data, pushing into array
const dynamoData = await dynamoDB.scan(scanParams).promise();
itemArray = itemArray.concat(dynamoData.Items);

if (dynamoData.LastEvaluatedKey) {
scanParams.ExclusiveStartkey = dynamoData.LastEvaluatedKey;
return await scanDynamoRecords(scanParams, itemArray);
}
return itemArray;} catch (err) {
console.log("ERROR in Scan Dynamo Records: ", err);
}
}// Add a Product
async function saveProduct(requestBody) {
const params = { TableName: dynamoDBTableName, Item: requestBody, }; return await dynamoDB .put(params) .promise() .then(() => { const body = { Operation: "SAVE", Message: "SUCCESS", Item: requestBody, }; return buildResponse(200, body); },(err) => { console.log("ERROR in Save Product: ", err); } );}async function modifyProduct(productId, updateKey, updateValue) { const params = { TableName: dynamoDBTableName, Key: { productId: productId, }, UpdateExpression: `set ${updateKey} = :value`, ExpressionAttributeValues: { ":value": updateValue, }, ReturnValues: "UPDATED_NEW", };return await dynamoDB .update(params) .promise() .then( (response) => { const body = { Operation: "UPDATE", Message: "SUCCESS", UpdatedAttributes: response, }; return buildResponse(200, body); }, (err) => { console.log("ERROR in Update Product: ", err); } );}// Delete a Productasync function deleteProduct(productId) { const params = { TableName: dynamoDBTableName, Key: { productId: productId, }, ReturnValues: "ALL_OLD", }; return await dynamoDB .delete(params) .promise() .then((response) => { const body = { Operation: "DELETE", Message: "SUCCESS", Item: response, }; return buildResponse(200, body); }, (err) => { console.log("ERROR in Delete Product: ", err); } ); }// For specific response structure
function buildResponse(statusCode, body) {
return {
statusCode,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
};
}

→ Copy-paste this code in the Lambda function. Click Test and deploy.

5. Finally let us open Postman and execute our serverless APIs:

a. Health: This API checks the health of the Lambda app

Endpoint:

https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/health

Type: GET

cURL:

curl --location --request GET 'https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/health'

b. Get Products:

Endpoint:

https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/products

Type: GET

cURL:

curl --location --request GET 'https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/products' \--header 'Content-Type: application/json' \--data-raw '{"productId": "100","productName": "Oximeter","price": 150,"color": "white","inventory": 1000}'

c. Get Specific Product:

Endpoint:

https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/product?productId=102

Type: GET

cURL:

curl --location --request GET 'https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/product?productId=102' \--header 'Content-Type: application/json' \--data-raw '{"productId": "100","productName": "Oximeter","price": 150,"color": "white","inventory": 1000}'

d. Add a Product

Endpoint:

https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/product

Type: POST

body:

{  "productId": "103",  "productName": "Nescafe Classic",  "price": 50,  "color": "classic",  "inventory": 2000}

cURL:

curl --location --request POST 'https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/product' \--header 'Content-Type: application/json' \--data-raw '{"productId": "103","productName": "Nescafe Classic","price": 50,"color": "classic","inventory": 2000}'

e. Update a Product:

Endpoint:

https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/product

Type: PATCH

body:

{"productId": "102","updateKey": "productName","updateValue": "Muscle Blaize Whey Gold"}

cURL:

curl --location --request PATCH 'https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/product' \--header 'Content-Type: application/json' \--data-raw '{"productId": "102","updateKey": "productName","updateValue": "Muscle Blaize Whey Gold"}'

f. Delete a Product:

Endpoint:

https://rlzwv202c6.execute-api.ap-south-1.amazonaws.com/prod/product

Type: DELETE

body:

{"productId": "103"}

Video:

https://secure.vidyard.com/organizations/2087856/players/8hNd43c7ifC1JC7Vsmac1q?edit=true&npsRecordControl=1

Repository:

https://github.com/AmirMustafa/serverless-crud-lambda

Closing Thoughts:

In this article, we have learned how to use AWS Lambda, DynamoDB, API Gateway service to create REST APIs. We have created, updated, deleted, and fetched products from Dynamo DB.

Thank you for reading till the end 🙌 . If you enjoyed this article or learned something new, support me by clicking the share button below to reach more people and/or give me a follow on Twitter to see some other tips, articles, and things I learn about and share there.

More content at plainenglish.io. Sign up for our free weekly newsletter. Get exclusive access to writing opportunities and advice in our community Discord.

--

--

JavaScript Specialist | Consultant | YouTuber 🎬. | AWS ☁️ | Docker 🐳 | Digital Nomad | Human. Connect with me on https://www.linkedin.com/in/amirmustafa1/