AWS Challenge # 2 - Serverless Web Apps using Amazon DynamoDB

Serverless Web Apps using Amazon DynamoDB

 

Objective

 

Build a mission dossier generator using DynamoDB by creating a DynamoDB table and add data, and then review the necessary IAM roles and policies that you will use to grant secure access to this data. Once you are done with it you will create Lambda functions that interact with DynamoDB and retrieve data in several different ways, then you will build an API through the API Gateway service and setting up a public website to retrieve information from your DynamoDB table via Lambda Functions. Finally, you will be able to generate mission dossiers.

By the end of this exercise, you will be able to:

  • Create and Amazon DynamoDB table
  • Add items to your Amazon DynamoDB table
  • Understand the structure of IAM roles and policies needed to access your table
  • Create Lambda functions that retrieve data in a DynamoDB table in two different ways
  • Test Lambda functions with a template
  • Configure custom test for Lamba functions
  • Create an API for Lambda functions to access a DynamoDB table using Amazon API Gateway
  • Generate the SDK for your API
  • Configure and publish content with Amazon S3

 

Estimated Duration: 170 minutes.

              

Execution

 

AWS Console

Create your DynamoDB table

 

  1. In the AWS Management Console, on the Services menu, click DynamoDB.
  2. Click Create table then configure:
    • Table name: SuperMission
    • Partition key:
    • SuperHero
    • String

You will use the default settings to create the table.

  1. Click Create Table.

The table will only take a few seconds to create. If your browser takes longer than this, click Cancel and refresh the web page. Your table should appear.

 

Add Items via Form Method

 

In this task, you will add items to the table using the Tree method

 

  1. Once the table creation is complete
    • Click Tables in the left panel
    • Select SuperMission under Tables
    • Click Create Item
  2. Confirm that Form is selected in the top-right of the Create item page.

The first Attribute name field is already populated for you with SuperHero.

  1. For Value: enter: Batman.
  2. To add the next attribute, click the Add new attribute and select String.
  3. Using the table below, enter each item as Attribute name and Value, one at a time.
  4. To add the next attribute, repeat step 7.

Attribute name

Value

SuperHero (already populated for you)

Batman

MissionStatus

In Progress

Villain1

Joker

Villain2

Bane

Villain3

Ras Al Ghul

SecretIdentity

Bruce Wayne

 

When you finish, your item should look like:

  1. Click Create Item.

Add Items via JSON

 

In this task, you will add an item as JSON

 

  1. Click Create item then configure:
  2. Confirm that JSON is selected in the top-right of the Create item page.
  3. Delete all text in the editor.
  4. Make sure View DynamoDB JSON toggle is off.

  1. Copy and paste this JSON code into the editor:

{

    "SuperHero": "Superman",

    "Villain1": "Doomsday",

    "Villain2": "General Zod",

    "Villain3": "Lex Luthor",

    "MissionStatus": "In progress",

    "SecretIdentity": "Clark Kent"

}

 

  1. Click Create item.
  2. Repeat the above steps to enter the third and fourth items.

Third item:

{

    "SuperHero": "The Winchester Brothers",

    "Villain1": "Vampires",

    "Villain2": "Ghosts",

    "Villain3": "Werewolves",

    "MissionStatus": "Complete",

    "SecretIdentity": "Sam and Dean"

}

 

Fourth item:

 

{

    "SuperHero": "Iron Man",

    "Villain1": "Apocalypse",

    "Villain2": "Doctor Doom",

    "Villain3": "Loki",

    "MissionStatus": "In progress",

    "SecretIdentity": "Tony Stark"

}

 

 

 

Review IAM Policies and Roles

 

In this task you will review the two IAM policies that will be used to access your data in the next part of this challenge.

 

  1. On the Services menu, click IAM.
  2. In the left navigation pane, click Policies.
  3. Create a Policy called SuperDynamoDBScanPolicy.
  4. Click in JSON tab and use the following policy:

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Action": [

                "dynamodb:Scan",

                "s3:GetObject",

                "s3:PutObject",

                "dynamodb:BatchWriteItem"

            ],

            "Resource": [

                "*"

            ],

            "Effect": "Allow"

        }

    ]

}

              

This is a simple policy that grants access to the Scan, BatchWriteItem APIs under DynamoDB and GetObject, PutObject APIs under S3 in your account.

 

  1. In the navigation pane, click Roles.
  2. Create a Lambda role named SuperDynamoDBScanRole and attach the policy you just created.
  3. In the left navigation pane, click Policies.
  4. Create a Policy called SuperDynamoDBQueryPolicy.
  5. Click in JSON tab and use the following policy:

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Condition": {

                "ForAllValues:StringEquals": {

                    "dynamodb:Attributes": [

                        "SuperHero",

                        "MissionStatus",

                        "Villain1",

                        "Villain2",

                        "Villain3"

                    ]

                }

            },

            "Action": [

                "dynamodb:Query"

            ],

            "Resource": "*",

            "Effect": "Allow"

        }

    ]

}

              

 

This policy allows the user or entity that assumes the role to perform a Query operation, but only against the specified attributes. This powerful feature enables you to implement column-level security on your DynamoDB tables. 

 

  1. In the navigation pane, click Roles.
  2. Create a Lambda role named SuperDynamoDBQueryRole and attach the policy you just created. 

 

 

 

 

Create your Lambda functions

 

In this task, you will create two Lambda functions. The first will retrieve a list of superheroes that are stored in the SuperMission DynamoDB table. The second will retrieve the mission details from the same DynamoDB table.

 

  1. On the Services menu, click Lambda.
  2. Click Create Function
  3. Select Author from scratch, then configure:
    • Function name: getheroeslist
    • Runtime: Node.js (latest version)
    • Expand -> Change default execution role
    • Execution role: use an existing role
    • Existing role: SuperDynamoDBScanRole
    • Click Create function
  4. Click Code tab and then in the Code source section, delete existing code in index.js 33. Within the index.js tab, paste the code below shown:

var doc = require('aws-sdk'); var dynamo = new doc.DynamoDB();

 

exports.handler = function(event, context) {     var getParams = {

        TableName:'SuperMission'

   };

 

    dynamo.scan(getParams, function(err, data){         if (err) console.log(err, err.stack); // an error occurred         else {              context.succeed(data);

        }

    });

};

  1. Examine the code and review what the function does.

This function retrieves a list of superheroes stored in the SuperMission DynamoDB table.

  1. Click Deploy.

  1. Click Test tab next to the Code tab and then configure:
    • Name: myTest
  2. In the editor, remove all the placeholder except {}

Removing {} will result in error: There is an error in your JSON event. Correct it before saving.

  1. Click Save changes
  2. Click Test
  3. Scroll the top of the page.

You will see a message telling you that Execution Result: Succeeded.

  1. Expand -> Details

You will see a text box displaying the contents of the SuperMission DynamoDB table.

 

Next, you will create a second Lambda function to retrieve the mission details from the SuperMission DynamoDB table.

  1. At the top of the page, click Functions.
  2. Click Create Function
  3. Select Author from scratch, then configure:
    • Function name: getmissiondetails
    • Runtime: Node.js (latest version)
    • Expand -> Change default execution role
    • Execution role: use an existing role
    • Existing role: SuperDynamoDBQueryRole
    • Click Create function
  4. Click Code tab and then in the Code source section, delete existing code in index.js 46. Within the index.js tab, paste the code below shown:

var doc = require('aws-sdk'); var dynamo = new doc.DynamoDB();

 

exports.handler = function(event, context) {     var condition = {};     condition["SuperHero"] = {

                ComparisonOperator: 'EQ',

                AttributeValueList:[{S: event.superhero}]

            }

 

    var getParams = {

        TableName:'SuperMission',

        ProjectionExpression:"SuperHero, MissionStatus, Villain1, Villain2, Villain3",

        KeyConditions: condition

   };

    dynamo.query(getParams, function(err, data){         if (err) console.log(err, err.stack); // an error occurred         else {              context.succeed(data);

        }

    });

};

 

  1. Examine the code and review what the function does.

This function retrieves the mission details from DynamoDB table.

  1. Click Deploy.

  1. Click Test tab next to the Code tab and then configure:

                    •     Name: myTest2

  1. In the editor, remove all the placeholder code.
  2. Copy the code below and paste it into the editor.

This code looks for details of Batman’s mission in the DynamoDB table.

{

  "superhero": "Batman"

}

 

  1. Click Save changes
  2. Click Test
  3. Scroll the top of the page.

You will see a message telling you that Execution Result: Succeeded. Below that, there will be a text box displaying the mission details for “Batman”.

  1. Expand -> Details

You will see a text box displaying the contents of the SuperMission DynamoDB table.

 

Create your Lambda functions

 

In this task, you will learn how to create and configure a new API. Amazon API Gateway helps developers to create and manage APIs to back-end systems running on Amazon EC2, AWS Lambda, or any publicfacing web service. You will create and configure an API to connect your DynamoDB table to your Lambda functions.

  1. On the Services menu, click API Gateway.
  2. From the API Types, click Build for the REST API type.
  3. On the Create Example API screen click OK.

This example API is prepopulated for you by AWS. It is a good reference if you are new to working with Amazon API Gateway, but you will not use this example for your lab.

  1. Click the New API radio button, then configure:
      • API name: SuperheroesMission
      • Description: Demo
      • Click Create API

 

You will see what looks like a file explorer.

  1. Click /

This is the root of the folder.

  1. On the Actions menu, click Create Resource, then configure:
      • Resource Name: getheroeslist
      • Click Create Resource

Next you will create another child resource.

  1. Click / (again, the roof of the folder).
  2. On the Actions menu, click Create Resource, then configure:
      • Resource Name: getmissiondetails
      • Click Create Resource

 

Next you will create the method to call the Lambda functions you created. Recall that the Lambda functions pull mission data from the DynamoDB table in several different ways.

  1. Click getheroeslist resource.
  2. On the Actions menu, click Create Method, then configure:
    • Click the drop-down and select POST
    • Click the check mark next to POST

Next you will set up your method.

In the Post – Setup window, configure:

    • Integration type: Lambda Function
    • Lambda Region: (the same one where you created your Lambda functions)
    • Lambda Function: getheroeslist
    • Click Save

You will see a warning that you are about to give the API Gateway service permission to invoke your Lambda function.

  1. Click OK.

  1. Click the getmissiondetails resource.
  2. On the Actions menu, click Create Method, then configure:
      • Click the drop-down and select POST.
      • Click the check mark next to POST.

In the Post – Setup windows, configure:

      • Integration type: Lambda Function
      • Lambda Region: (the same one where you created your Lambda functions)
      • Lambda Function: getmissiondetails
      • Click Save You will see the same warning.
  1. Click OK.

Next, you will enable CORS for the methods you just created in the API Gateway. Cross-Origin Resource Sharing (CORS) allows browser to make HTTP request to servers with a different domain/origin.

 

  1. Click the getheroeslist resource.
  2. On the Actions menu, click Enable CORS, then configure:
      • Ensure Post and Options, are selected
      • Click Enable CORS and replace existing CORS headers
  3. Click Yes, replace existing values

 

Repeat the same process for getmissiondetails resource.

 

  1. Click the root folder/.
  2. On the Actions menu, click Deploy API, then configure:

•     Deployment stage: New Stage •        Stage name: Demo1 •      Click Deploy.

You have deployed the API.

 

Generate the SDK For Your API

 

In this task, you will generate the SDK for your API. After deploying the API, you will be redirected to the Demo1 Stage Editor.

  1. On the stage editor, click the SDK Generation tab, then configure:
    • Platform: Javascript
    • Click Generate SDK

 

  1. Save the zip file to a location on your computer.
  2. Extract the content of the JavaScript zip file you downloaded.

Next, you will retrieve an HTML page that you will use to test your API.

  1. Create an HTML page and paste the code below, save it in the apiGateway-js-sdk folder.

Recall that you downloaded the ZIP file and extracted the contents to your local computer in the previous steps.

 

<!DOCTYPE html>

<html lang="ja"><head>

<meta http-equiv="content-type" content="text/html; charset=windows-1252">

  <script type="text/javascript" src="lib/axios/dist/axios.standalone.js"></script>

  <script type="text/javascript" src="lib/CryptoJS/rollups/hmac-sha256.js"></script>

  <script type="text/javascript" src="lib/CryptoJS/rollups/sha256.js"></script>

  <script type="text/javascript" src="lib/CryptoJS/components/hmac.js"></script>

  <script type="text/javascript" src="lib/CryptoJS/components/enc-base64.js"></script>

  <script type="text/javascript" src="lib/url-template/url-template.js"></script>

  <script type="text/javascript" src="lib/apiGatewayCore/sigV4Client.js"></script>

  <script type="text/javascript" src="lib/apiGatewayCore/apiGatewayClient.js"></script>

  <script type="text/javascript" src="lib/apiGatewayCore/simpleHttpClient.js"></script>

 

 

 

<script type="text/javascript" src="lib/apiGatewayCore/utils.js"></script>

  <script type="text/javascript" src="apigClient.js">

</script>

  </head>

  <body>

   <form id="callapigateway" name="callapigateway">

    <br><br>

    <label for="superHero">Today's Super Hero Mission</label><br>     <br><br>

    <select id="superheroname">

     <option selected="selected">What's your Super Hero name?</option>     </select> <!-- Will be populated later-->

 

    <button type="button" value="inputsbutton" onclick="callapigw()">GO</button>     <br><br>

    <label for="superHero">Mission Status</label>

    <br><input type="text" length="50" name="missionstatus" id="missionstatus" placeholder="Current Mission status">

    <br><br>

    <label for="superHero">Mission Dossier</label>

    <br><textarea style="font-size: 28pt" cols="20" rows="5" name="List for today" id="missiondossier"></textarea>

 

  </form>

  <script type="text/javascript">    var apigClient = apigClientFactory.newClient();

 

    apigClient.getheroeslistPost({}, {})

     .then(function(result){       var items = result.data.Items;

 

 

console.log(items);       var x = document.getElementById("superheroname");

      for (var i = 0; i < items.length; i++){        var option = document.createElement("option");        option.text = items[i].SuperHero.S;

       x.add(option);

       //option.value = items[i].SuperHero.S;

       //select.appendChild(option);

      }

     }).catch( function(result){

     //This is where you would put an error callback

  });

 

  function callapigw() {     var apigClient = apigClientFactory.newClient();     var x = document.getElementById("superheroname");     var iSuperHero = x.options[x.selectedIndex].value;

 

    if (iSuperHero == null || iSuperHero == "") {return false;}     var body = "{\"superhero\":\"" + iSuperHero + "\"}";     apigClient.getmissiondetailsPost({}, body, {})

        .then(function(result){             var dataoutput = result.data.Items[0];             console.log(dataoutput);             document.getElementById("missionstatus").value = dataoutput.MissionStatus.S

            document.getElementById("missiondossier").value = dataoutput.Villain1.S + "\n" + dataoutput.Villain2.S + "\n" + dataoutput.Villain3.S + "\n";

        }).catch( function(result){           document.write(result);

    });

  }  </script>

</body></html>

  1. Open the index.html in your browser.
  2. Using the index web page, retrieve mission details.
  3. Review the output.

You will notice that the drop-down list uses the API Gateway resource getheroeslist to invoke the getheroeslistFunction Lambda function.

Publish with S3

 

  1. On the Services menu, click S3.
  2. Click Create bucket then configure:
      • Bucket name: mybucketNUMBER
      • Replace NUMBER with a random number – bucket names must be unique
      • Copy the name of the bucket to your editor
      • Region: (same region where you created your Lambda functions and APIs)
  3. In the Bucket settings for Block Public Access:
    • De-select Block all public access.
    • Select I acknowledge that….
  4. Scroll to the bottom of the screen, then click Create bucket
  5. Click your bucket to open it.
  6. Click the Permissions tab.
  7. Click Edit on the Bucket policy section
  8. Copy the following bucket policy and paste it into the editor:

{

  "Version": "2012-10-17",

  "Statement": [

    {

      "Sid": "PublicReadForGetBucketObjects",

      "Effect": "Allow",

      "Principal": "*",

      "Action": "s3:GetObject",

      "Resource": "arn:aws:s3:::BUCKET/*"

    }

  ]

}

 

  1. Replace BUCKET with the name of your bucket.
  2. Click Save Changes.

 

  1. Click Properties tab, then click Edit on the Static website hosting section

                    •     Select Enable Static website hosting

  1. Configure the following:
        • Hosting Type: Host a static website
        • Index document: index.html
        • Error document: index.html

              

Note – We have used the same document for both Index and Error, as some browsers display their own error message when an error occurs, ignoring the error document that Amazon S3 returns. For example, when an HTTP 404 Not Found error occurs, Google Chrome might ignore the error document that Amazon S3 returns and displays its own error.

  1. Click Save Changes

 

  1. Click the Objects tab, then:
      • Using your computer’s file system explorer, locate the folder (apiGateway-jf-sdk) that you saved the index.html file in.
      • Drag and then drop the entire apiGateway-jf-sdk folder to the Overview tab area.
  2. Click Upload.
  3. This will upload the folder and its contents to your S3 bucket.
  4. Wait until the upload is complete.

  1. Open a new browser tab/window, then: http://BUCKET.s3-website-YOUR_REGION.amazonws.com/
      • Replace BUCKET with the name of your bucket
      • Replace YOUR_REGION with the name of the region where you created your bucket.
      • Press Enter
  2. You can now look up mission dossier data stored in your DynamoDB database.

What’s next?

    • Add manually your own mission dossiers to the SuperMission DynamoDB table and then refresh your page.
    • Create a Lambda function and API Gateway method to add mission dossiers through your API.
    • Update your S3 website to be accessed through a CloudFront distribution instead of being public.
    • If you have a Route53 site, create a record to redirect your S3 website (or CloudFront distribution) and make it accessible through your domain.

 

Clean up

Remember to delete:

  • DynamoDB table
  • IAM roles and policies
  • Lambda functions
  • API Gateway
  • S3 bucket

              

References: 

  • Amazon Lambda Documentation

(http://docs.aws.amazon.com/lambda/latest/dg/welcome.html)

(https://docs.aws.amazon.com/iam/index.html)

  • Amazon API Gateway Documentation

(https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html)