Serverless Backend

In this module, you will create a backend for handling requests for the web application using AWS Lambda and Amazon DynamoDB.

step3

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:

  1. Write logs to Amazon CloudWatch Logs

  2. 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

requestUnicorn.js
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.

test-event.json
{
  "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.

Quick Navigation

Arch AWS Amplify 32 HOST A STATIC WEBSITE

Arch Amazon Cognito 32 MANAGE USERS

Arch AWS Lambda 32 BUILD A SERVERLESS BACKEND

Arch Amazon API Gateway 32 DEPLOY A RESTFUL API