Getting Started with AWS

Build a Serverless Web Application

with AWS Lambda, Amazon API Gateway, AWS Amplify, Amazon DynamoDB, and Amazon Cognito

Module 3: Serverless Service Backend

You will use AWS Lambda and Amazon DynamoDB to build a backend process for handling requests for your web application

Overview

In this module, you will use AWS Lambda and Amazon DynamoDB to build a backend process for handling requests for your web application. The browser application that you deployed in the first module allows users to request that a unicorn be sent to a location of their choice. To fulfill those requests, the JavaScript running in the browser will need to invoke a service running in the cloud.

Architecture overview

Architecture Overview

You will implement a Lambda function that will be invoked each time a user requests a unicorn. The function will select a unicorn from the fleet, record the request in a DynamoDB table, and then respond to the frontend application with details about the unicorn being dispatched.

The function is invoked from the browser using Amazon API Gateway. You'll implement that connection in the next module. For this module, you will just test your function in isolation.

 Time to complete

30 minutes

 Services used

Implementation

  • Use the Amazon DynamoDB console to create a new DynamoDB table. 

    1. In the Amazon DynamoDB console, choose Create table.
    2. For the Table name, enter Rides. This field is case sensitive.
    3. For the Partition key, enter RideId and select String for the key type. This field is case sensitive.
    4. In the Table settings section, ensure Default settings is selected, and choose Create table
    5. On the Tables page, wait for your table creation to complete. Once it is completed, the status will say Active. Select your table name.
    6. In the Overview tab > General Information section of your new table and choose Additional info. Copy the ARN. You will use this in the next section.
  • Every Lambda function has an IAM role associated with it. This role defines what other AWS services the function is allowed to interact with. For the purposes of this tutorial, you'll need to create an IAM role that grants your Lambda function permission to write logs to Amazon CloudWatch Logs and access to write items to your DynamoDB table.

    1. In the IAM console, select Roles in the left navigation pane and then choose Create Role.
    2. In the Trusted Entity Type section, select AWS service. For Use case, select Lambda, then choose Next
      Note: Selecting a role type automatically creates a trust policy for your role that allows AWS services to assume this role on your behalf. If you are creating this role using the CLI, AWS CloudFormation, or another mechanism, specify a trust policy directly.
    3. Enter AWSLambdaBasicExecutionRole in the filter text box and press Enter
    4. Select the checkbox next to the AWSLambdaBasicExecutionRole policy name and choose Next.
    5. Enter WildRydesLambda for the Role Name. Keep the default settings for the other parameters.
    6. Choose Create Role.
    7. In the filter box on the Roles page type WildRydesLambda and select the name of the role you just created.
    8. On the Permissions tab, under Add permissions, choose Create Inline Policy.
    9. In the Select a service section, type DynamoDB into the search bar, and select DynamoDB when it appears.
    10. Choose Select actions.
    11. In the Actions allowed section, type PutItem into the search bar and select the checkbox next to PutItem when it appears.
    12. In the Resources section, with the Specific option selected, choose the Add ARN link.
    13. Select the Text tab. Paste the ARN of the table you created in DynamoDB (Step 6 in the previous section), and choose Add ARNs.
    14. Choose Next.
    15. Enter DynamoDBWriteAccess for the policy name and choose Create policy.
  • AWS Lambda will run your code in response to events such as an HTTP request. In this step you'll build the core function that will process API requests from the web application to dispatch a unicorn. In the next module you'll use Amazon API Gateway to create a RESTful API that will expose an HTTP endpoint that can be invoked from your users' browsers. You'll then connect the Lambda function you create in this step to that API in order to create a fully functional backend for your web application.

    Use the AWS Lambda console to create a new Lambda function called RequestUnicorn that will process the API requests. Use the following requestUnicorn.js example implementation for your function code. Just copy and paste from that file into the AWS Lambda console's editor.

    Make sure to configure your function to use the WildRydesLambda IAM role you created in the previous section.

    1. From the AWS Lambda console, choose Create a function.
    2. Keep the default Author from scratch card selected.
    3. Enter RequestUnicorn in the Function name field.
    4. Select Node.js 16.x for the Runtime (newer versions of Node.js will not work in this tutorial).
    5. Select Use an existing role from the Change default execution role dropdown.
    6. Select WildRydesLambda from the Existing Role dropdown.
    7. Click on Create function.
    8. Scroll down to the Code source section and replace the existing code in the index.js code editor with the contents of requestUnicorn.js. The following code block displays the requestUnicorn.js file. Copy and paste this code into the index.js tab of the code editor.
    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': '*',
        },
      });
    }

        9. Choose Deploy.

  • For this module you will test the function that you built using the AWS Lambda console. In the next module you will add a REST API with API Gateway so you can invoke your function from the browser-based application that you deployed in the first module.

    1. In the RequestUnicorn function you built in the previous section, choose Test in the Code source section, and select Configure test event from the dropdown.
    2. Keep the Create new event default selection.
    3. Enter TestRequestEvent in the Event name field.
    4. Copy and paste the following test event 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}}"
    }

        5. Choose Save.

        6. In the Code source section of your function, choose Test and select TestRequestEvent from the dropdown.

        7.  On the Test tab, choose Test.

        8. In the Executing function:succeeded message that appears, expand the Details dropdown.

        9. 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": "*"
        }
    }

Was this page helpful?

Deploy a RESTful API