CRUD REST API implementation using Amazon API Gateway, AWS Lambda and Amazon DynamoDB

 In case you are thinking how to create a serverless CRUD application and expose the same to your external world, then follow the below steps, you will find a detailed understanding of it I believe.

Serverless CRUD REST API Implementation

First and the foremost thing is that you need to have an AWS account. In case you are new, you can create a free account https://aws.amazon.com/free where you will receive a lot of free tier resources to play around and have the feeling of cloud.

For the past few years, Serverless Computing is a hot topic. But what is it? It is basically a cloud computing execution model in which the cloud provider is in charge of creating machine resources on demand and taking care of the servers on behalf of their customers. The customers responsibility is to provide the code. AWS Lambda service provides this feature of serverless computing.

Here, we are going to use these services —Amazon API Gateway, AWS Lambda and Amazon DynamoDB.

I have considered a Data Model — Orders with three attributes — oid(string), oname(string) and oquant(int) and this informations would be stored in JSON format in Amazon DynamoDB.

Item: {
“oid”: requestBody.oid,
“oname”: requestBody.oname,
“oquant”: requestBody.oquant
}
DynamoDB Console

AWS Lambda would perform the CRUD operations — I have used nodeJS to write the implementations. You also need to create an IAM service role with Policy which would allow the Lambda to invoke DynamoDB apis for CRUD operations and CloudWatch apis for log storage.

Lambda Console

Inorder to work with AWS-SDK DynamoDB, you can refer to — https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html

Attaching the lambda code written in NodeJS below, you can definitely modify it or improve for differnt scenarios as per your requirements :—

const AWS = require("aws-sdk");const dynamo = new AWS.DynamoDB.DocumentClient();
const TableName = "tbl_orders";
exports.handler = async (event, context) => {
let body;
let statusCode = 200;
const headers = {
"Content-Type": "application/json"
};
console.log(JSON.stringify(event)+"\n");
try {
if(event.httpMethod=='GET'){
if(JSON.stringify(event.pathParameters).includes('oid')){
body = await dynamo.get({
TableName: TableName,
Key: {
"oid" : event.pathParameters.oid
}
}).promise();
}else{
body = await dynamo.scan({ TableName: TableName }).promise();
}
} else if(event.httpMethod=='PUT'){
let requestBody = JSON.parse(event.body);
await dynamo
.put({
TableName: TableName,
Item: {
"oid": requestBody.oid,
"oname": requestBody.oname,
"oquant": requestBody.oquant
}
}).promise();
body = `Put item ${requestBody.oid}`;
} else if (event.httpMethod=='DELETE'){
if(JSON.stringify(event.pathParameters).includes('oid')){
await dynamo.delete({
TableName: TableName,
Key: {
"oid" : event.pathParameters.oid
}
}).promise();
body = `Deleted item ${event.pathParameters.oid}`;
}else{
throw new Error('oid absent');
}
} else{
throw new Error('Not a valid httpMethod');
}
} catch (err) {
statusCode = 400;
body = err.message;
} finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers
};
};

For testing the sanity of the above Lambda code you can refer to the below event json where I have kept the most important fields in bold. Whenever we are going to enable Lambda proxy integration of API Gateway for a particular method an event json containing all the below fields would be sent to Lambda. I will discuss about proxy integration somewhere later in this blog.

{
"body": {
"oid": "rgw99",
"oname": "walkytalky",
"oquant": 7
}
,
"resource": "/{proxy+}",
"path": "/orders",
"httpMethod": "PUT",
"isBase64Encoded": true,
"queryStringParameters": {
"foo": "bar"
},
"multiValueQueryStringParameters": {
"foo": [
"bar"
]
},
"pathParameters": {
"proxy": "/path/to/resource"
}
,
"stageVariables": {
"baz": "qux"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8",
"Cache-Control": "max-age=0",
"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": "US",
"Host": "1234567890.execute-api.us-east-2.amazonaws.com",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Custom User Agent String",
"Via": "1.1 xyz12345.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "xyz12345",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"multiValueHeaders": {
"Accept": [
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
],
"Accept-Encoding": [
"gzip, deflate, sdch"
],
"Accept-Language": [
"en-US,en;q=0.8"
],
"Cache-Control": [
"max-age=0"
],
"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": [
"US"
],
"Host": [
"0123456789.execute-api.us-east-2.amazonaws.com"
],
"Upgrade-Insecure-Requests": [
"1"
],
"User-Agent": [
"Custom User Agent String"
],
"Via": [
"1.1 abcdefghijkl.cloudfront.net (CloudFront)"
],
"X-Amz-Cf-Id": [
"cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA=="
],
"X-Forwarded-For": [
"127.0.0.1, 127.0.0.2"
],
"X-Forwarded-Port": [
"443"
],
"X-Forwarded-Proto": [
"https"
]
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "123456",
"stage": "prod",
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
"requestTime": "09/Apr/2015:12:34:56 +0000",
"requestTimeEpoch": 1428582896000,
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"accessKey": null,
"sourceIp": "127.0.0.1",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "Custom User Agent String",
"user": null
},
"path": "/prod/path/to/resource",
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"apiId": "1234567890",
"protocol": "HTTP/1.1"
}
}

Finally, after testing we are going to expose this CRUD operations of the Lambda code through REST endpoints using AWS API Gateway proxy integration.

I have created a REST API using API Gateway and created a resource orders, and exposed below methods for different functionality -

GET /orders
PUT /orders
GET /orders/{id}
DELETE /orders/{id}

Once you create these methods from the Actions option you will be able to integrate those individual methods with different types of backends.

You need to select the ‘Integration Request’ to enable the type of integration that you want to do.

There can be different types of integration as below. Here I have selected Lambda Function and marked the Use Lambda Proxy Integration checkbox to send the entire request json that was made to API Gateway, directly to Lambda. In the field of Lambda Function provide the name of the function as well as attach the Execution service-role of the Lambda to perform DynamoDB and CloudWatch api calls.

Once the integration is completed, perform few testing operations -

Click on Thunder symbol below Test

Once you land on to this page, click on Test button below right hand side.

Once you click the Test button, you will be able to see response coming from the Lambda function —

Once the test is done your API is ready to deploy. Click on Deploy API under Actions -

Once you deploy, you will get the option of Deployment stage where you have to select a stage — it can be like this dev, test, prod, demo etc. as per your choice of name. I have used default stage. It is basically used to segregate API deployment environment wise.

Once you have deployed the api, you will get the Invoke URL page for external use.

API Gateway Console

Try to hit the REST endpoints from Postman or operate the curl command to see the responses sent by the endpoints.

Use CloudWatch Logs to check if your code is executed successfully or not:

Conclusion

It’s really that simple! I hope you have found this blog useful and feel more confident about building something similar and more.

Important links:

Comments

Popular posts from this blog

Kubernetes: A Synopsis and Introduction

Kubernetes : Pod Communications, Deployments and ReplicaSets

Handle errors in Amazon API Gateway from integrated Lambda functions