Serverless Backend
In this module, you will create a backend for handling requests for the web application using AWS Lambda and Amazon DynamoDB.
Create an Amazon DynamoDB table
In the DynamoDB console, choose Create table
Provide the details as follows:
Table name: Rides
Partition key: RideId of type String
Table settings: Default
Click on Create table
Once th table is created and status is Active
, make a note of the ARN of the table.
Create an IAM role for the Lambda function
The IAM role associated with a Lambda function defines which AWS services the function is allowed to interact with.
In this example, we need to create an IAM role that grants the following permissions:
-
Write logs to Amazon CloudWatch Logs
-
Write items to DynamoDB table
In the IAM console, select Roles
and click Create role.
In the Trusted Entity Type
section, select AWS service
. For Use case
, select Lambda
, then click Next.
Add the AWSLambdaBasicExecutionRole
policy.
Add the role name as WildRydesLambda
and click on Create role.
From the Roles
page, filter on WildRydesLambda
role and view the details.
On the Permissions
tab, under Add permissions
, choose Create Inline Policy
.
In the Select a service
section, type DynamoDB
into the search bar, and select DynamoDB
when it appears.
In the Actions allowed
section, type PutItem
into the search bar and select the checkbox next to PutItem
when it appears.
In the Resources
section, with the Specific
option selected, choose the Add ARN link
.
Select the Text
tab. Paste the ARN of the table you created in DynamoDB (Step 6 in the previous section), and choose Add ARNs
.
Click Next.
Enter DynamoDBWriteAccess
for the policy name and click Create policy.
Create a Lambda function for handling requests
From the AWS Lambda console, choose Create function.
Select Author from scratch
.
Add RequestUnicorn
as the Function name
.
Select the runtime as Node.js 16.x
Select Use an existing role
from the Change default execution role
dropdown.
Select WildRydesLambda
from the Existing Role
dropdown.
Click on Create function.
Add the content of the following file in the index.js
file and then click Deploy
const randomBytes = require('crypto').randomBytes;
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();
const fleet = [
{
Name: 'Angel',
Color: 'White',
Gender: 'Female',
},
{
Name: 'Gil',
Color: 'White',
Gender: 'Male',
},
{
Name: 'Rocinante',
Color: 'Yellow',
Gender: 'Female',
},
];
exports.handler = (event, context, callback) => {
if (!event.requestContext.authorizer) {
errorResponse('Authorization not configured', context.awsRequestId, callback);
return;
}
const rideId = toUrlString(randomBytes(16));
console.log('Received event (', rideId, '): ', event);
// Because we're using a Cognito User Pools authorizer, all of the claims
// included in the authentication token are provided in the request context.
// This includes the username as well as other attributes.
const username = event.requestContext.authorizer.claims['cognito:username'];
// The body field of the event in a proxy integration is a raw string.
// In order to extract meaningful values, we need to first parse this string
// into an object. A more robust implementation might inspect the Content-Type
// header first and use a different parsing strategy based on that value.
const requestBody = JSON.parse(event.body);
const pickupLocation = requestBody.PickupLocation;
const unicorn = findUnicorn(pickupLocation);
recordRide(rideId, username, unicorn).then(() => {
// You can use the callback function to provide a return value from your Node.js
// Lambda functions. The first parameter is used for failed invocations. The
// second parameter specifies the result data of the invocation.
// Because this Lambda function is called by an API Gateway proxy integration
// the result object must use the following structure.
callback(null, {
statusCode: 201,
body: JSON.stringify({
RideId: rideId,
Unicorn: unicorn,
Eta: '30 seconds',
Rider: username,
}),
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}).catch((err) => {
console.error(err);
// If there is an error during processing, catch it and return
// from the Lambda function successfully. Specify a 500 HTTP status
// code and provide an error message in the body. This will provide a
// more meaningful error response to the end client.
errorResponse(err.message, context.awsRequestId, callback)
});
};
// This is where you would implement logic to find the optimal unicorn for
// this ride (possibly invoking another Lambda function as a microservice.)
// For simplicity, we'll just pick a unicorn at random.
function findUnicorn(pickupLocation) {
console.log('Finding unicorn for ', pickupLocation.Latitude, ', ', pickupLocation.Longitude);
return fleet[Math.floor(Math.random() * fleet.length)];
}
function recordRide(rideId, username, unicorn) {
return ddb.put({
TableName: 'Rides',
Item: {
RideId: rideId,
User: username,
Unicorn: unicorn,
RequestTime: new Date().toISOString(),
},
}).promise();
}
function toUrlString(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
function errorResponse(errorMessage, awsRequestId, callback) {
callback(null, {
statusCode: 500,
body: JSON.stringify({
Error: errorMessage,
Reference: awsRequestId,
}),
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}
Validate your implementation
In the RequestUnicorn
function choose Test
in the Code source section, and select Configure test event
from the dropdown.
Keep the Create new event
default selection.
Enter TestRequestEvent
in the Event name
field.
Copy and paste the following into the Event JSON
section.
{
"path": "/ride",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Authorization": "eyJraWQiOiJLTzRVMWZs",
"content-type": "application/json; charset=UTF-8"
},
"queryStringParameters": null,
"pathParameters": null,
"requestContext": {
"authorizer": {
"claims": {
"cognito:username": "the_username"
}
}
},
"body": "{\"PickupLocation\":{\"Latitude\":47.6174755835663,\"Longitude\":-122.28837066650185}}"
}
Click Save
In the Code source
section of your function, choose Test
and select TestRequestEvent
from the dropdown.
On the Test
tab, choose Test
.
In the Executing function:succeeded
message that appears, expand the Details
dropdown.
Verify that the function result looks like the following:
{
"statusCode": 201,
"body": "{\"RideId\":\"SvLnijIAtg6inAFUBRT+Fg==\",\"Unicorn\":{\"Name\":\"Rocinante\",\"Color\":\"Yellow\",\"Gender\":\"Female\"},\"Eta\":\"30 seconds\"}",
"headers": {
"Access-Control-Allow-Origin": "*"
}
}
Conclusion
In this section, we created a Lambda function that stores data in DynamoDB and wrote a test to validate it.