Create your first app on AWS (Node Version)

I created the following notes after watching this great video. The video uses Python, but I adapted my version to use NodeJS instead. I noticed that there have been some updates to the AWS console since the video was made. So, I tried mention them in my notes.

Set up a website on Amplify

Create an HTML file named index.html and put this code into it.

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>My First App</title>
	<script type="text/javascript">

	window.addEventListener("load", () => {

		const urlToNodeLambda = "PUT API URLS HERE!!!!";
		const urlToPythonLambda = "PUT API URLS HERE!!!!";
		
		function doPost(url){

			const txtInput = document.querySelector("[name='txt']").value;


			// stringify the data and put it into the body of the post
			var data = {"someProp": txtInput};
			var body = JSON.stringify(data);
			
			// set the headers
			var headers = new Headers();
			headers.append("Content-Type", "application/json");
			

			var requestOptions = {
			    method: 'POST',
			    headers: headers,
			    body: body,
			    redirect: 'follow'
			};

			return fetch(url, requestOptions)
					  .then(response => response.text())
					  .catch(error => console.log('error', error))
					  .then(result => {
					  	const responseText = result;
					  	alert(responseText);
					  });	
		}


		document.querySelector("[name='btnNode']").addEventListener("click", async ()=>{
			await doPost(urlToNodeLambda);
			alert("Done with post to node lambda");
		});

		document.querySelector("[name='btnPython']").addEventListener("click", async ()=>{
			await doPost(urlToPythonLambda);
			alert("Done with post to python lambda");
		});


	});	

	</script>
</head>
<body>
	<h1>My First App</h1>
	<input type="button" name="btnNode" value="Post to Node Lambda">
	<input type="button" name="btnPython" value="Post to Python Lambda">
	<input type="text" name="txt" value="Enter something to post (JSON???)">
</body>
</html>

Now compress the index.html file into a .zip file. We'll upload it to AWS Amplify in the next step.

Then log into the AWS control panel, and navigate to Amplify (you can just type 'Amplify' into the search bar).

  1. Click on All Apps on the left.
  2. If this is your first app then click Host your web app (Get started). Otherwise, you should see a New app menu button and choose (the first time I logged in I fumbled a bit to figure out how to create my first app)
  3. Click the Deploy without Git provider radio button, and then click the Continue button.
  4. Enter a name in the App name textbox (I entered 'myFirstApp'), and enter 'dev' in the Environment name textbox.
  5. Then drop your zip file in (upload it).
  6. Then click the Save and deploy button. You may have to refresh the browser window (as mentioned in the video).
  7. Then you should be able to see a link to your project. So you navigate to that URL in your browser to see your web app.

You can also find links for your app in the Domain management section

Create a lambda function

In the AWS console navigate to AWS Lambda

Note that I fumbled a bit here because it seems like the AWS console is now slightly different than what it was when the video was made. Not sure, but the thing that I think I was missing was that I didn't deploy my function before testing it. I'll put reminders in my notes below.

  1. Click Create function
  2. Choose Author from scratch
  3. Enter a name for your function, I used myFirstFunction.
  4. I left the runtime at NodeJS and the arhitecture at x86_64,
  5. Click Create function button

Scroll down to see the starter code, this is what it looked like for me:

export const handler = async (event) => {
  // TODO implement
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};

Here's what it looked like after I added some sample code (It really just regurgitates what's in the event object, which are the parameters that get passed to our lambda function, you'll see those in a minute):

export const handler = async (event) => {
  
  console.log("Here's the event:", event);
  
  // loop through the event object an concatenate the key/value pairs into a string
  let eventProps = "";
  
  for (let key in event) {
    eventProps += key + ": " + event[key] + "<br>";
  }
  
  const response = {
    statusCode: 200,
    body: JSON.stringify(eventProps),
  };
  return response;
};

Note: To test your function, you must first deploy it. So, every time you make a change to the code, 'deploy' your app so that you can test the changes. I'll remind you of that, but first we need to create an event that will trigger our lambda function so we can test it.

Create/configure an event

  1. In the Test menu button, choose Configure test event
  2. Give the event a name (ex: myFirstEvent)
  3. You can leave it Private (the default)
  4. Click Save button
  5. Make sure to Deploy the function (press the Deploy button)

Now you should be able to switch to the Test tab and run your test from there by pressing the Test button. From there you can also look at the console log output in the log output section.

API Gateway

In the AWS console, click the Build button for REST API (I chose the non-private one). Then I saw some message about how AWS created the Swagger example app that I could import for my first Gateway.

Then I saw a screen with radio buttons, leave REST selected for the first group (the other option was WebSocket), and select the one for New API from the second group (the other options were Import Swagger Example and Example API)

Then enter a name (I entered myFirstApi).

The click the Create API button.

Create a method for your new API

In the Resources section of your new API, click the Actions menu button and choose Create method

Then set the select box that appears to POST

Click the checkmark next to the select box that you just set to POST, it will take you to the POST Setup form.

For Integration type choose Lambda Function.

Then enter the name of your lambda function in the Lambda Function textbox (I entered myFirstFunction)

Click the Save button. Then click the OK button (note that by clicking OK, you are giving your API Gateway the invoke permission for the lambda function).

Now you need to allow CORS requests to your API. So click the Actions menu button again and choose Enable CORS. A form will appear with info about the server response headers, you can leave everything as the defaults, and just click the Enable CORS and replace existing CORS headers button.

A popup window appears summarizing the CORS options and asking you to confirm them. Click the button that says Yes, replace existing values

Now you need to Deploy the API, so click the Actions menu button again and choose Deploy API (note that this will move you from the Resources section/tab to the Stages section/tab for your API). A popup appears asking you to set up the Deployment stage, choose [New Stage] from the drop down, and enter 'dev' in the Stage name textbox. Then click the Deploy button.

When the popup disappears, you should see an Invoke URL, which you should copy and save for later.

Click Resources to go back to the Resources section/tab for your API. There you can see that your API will handle OPTIONS requests (for CORS) and POST requests. For each you'll see a diagram of what happens when those requests come in. For POSTs, notice that there will be an Integration request to your lambda function.

To try out the POST, click the TEST button (with the blue lightning bolt). Before running the test, set the request body to some sort of JSON object (our lamba function will just regurgitate the properties in this object). Here's what I typed into the Request Body textarea:

{
	"msg":"Hello AWS!!!!!",
	"msg2":"This is cool!"
}

Then click the Test button. Hopefully you'll see the response object that includes the properties and values that you sent in the request.

You can now make POST requests to your API, which will trigger your lamba function, which simply regurgitates the data that is sent in the body of the POST request.

DynamoDB

DynamoDB is a key/value db. The key must be a string, and the value usually has to be a string (in a key/value db). I need to look this up.

We'll need to give our lambda function permission to write the the db we create.

In the AWS Console, navigate to DynamoDB.

Then click the Create table button.

In the form that appears, enter a table name (I entered myFirstTable).

For the Partition key enter 'ID'. (NOTE: ID will be used in a later step, when we put an item into a table).

Leave everything else in the form as is, and press the Create table button.

Now we need to save the ARN for the table (the Amazon Resource Name), so click on the link for the table that you just created. Then make sure you are in the Overview tab and in the General Info section you should be able to click Additional info to find the ARN for your table. Copy the ARN and save it for later

Allowing the lambda function to have permissions write to your table

In the AWS console, navigate to AWS Lambda, and click on your lambda function (mine is myFirstFunction). Then click on the Configuration tab.

Then click on the Permissions section. At the top you should see a role for your function. And next to it there should be an icon to open the role in a pop up window. Click on the icon.

Then click the Add permissions menu button and choose Create inline policy. You could set this up in Visual mode, but for this we'll use JSON mode, so click on the JSON tab.

Here's what the default settings were for the policy (note that this JSON is different than what I saw in the video):

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "Statement1",
			"Effect": "Allow",
			"Action": [],
			"Resource": []
		}
	]
}

Here's what it looked like after I made my changes:

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "AllowMyFirstFunctionForMyFirstDynamoTable",
			"Effect": "Allow",
			"Action": [
				"dynamodb:PutItem",
	            "dynamodb:DeleteItem",
	            "dynamodb:GetItem",
	            "dynamodb:Scan",
	            "dynamodb:Query",
	            "dynamodb:UpdateItem"
			],
			"Resource": [
				"PASTE YOUR ARN FOR YOUR TABLE HERE (AMAZON RESOURCE NAME)"
			]
		}
	]
}

Note that you will have to use your own ARN that you created earlier, for your dynamodb table. but you will have to use the one that you copied. Here's what we are doing with this policy:

  1. We give it a unique identifier by setting the Sid property.
  2. In the actions array, we are setting all the actions allowed to work with the DynamoDb
  3. In the Resources array, we specify the table(s) that this policy applies to.

Now click the Next button (I think this was a little different from the video, because she clicked on 'Review Policy').

Enter a name for the policy (I entered 'myFirstPolicy').

Then click the Create policy button.

Now we can update the lambda function to include code that writes to the database. So, in the AWS console, navigate to Lambda and then click on the link to your lambda function.

Update the code for your function to look like this:

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";

export const handler = async (event) => {
  
  const client = new DynamoDBClient({ region: 'us-east-2'});
  const docClient = DynamoDBDocumentClient.from(client);
  
  // To put an item into the table, we'll use the timestamp for the key
  // and the stringifed event for the value
  const timestamp = new Date().getTime();
  const timestampString = '' + timestamp; // The ID key (below) must be a string
  const eventString = JSON.stringify(event);
  
  const item = {
    ID: timestampString,
    value: eventString 
  };
  //Note you could use event (the obj) instead of the eventString
  //but the data looks weird in the DB (I need to reserarch it!) 

  console.log(item);
  
  const command = new PutCommand({
    TableName: "myFirstTable",
    Item: item,
  });

  const dbResponse = await docClient.send(command);
  return dbResponse;

};

Note: In my ARN, the region was 'us-east-2' so that's what a used in the code above. You may have to change this to match the region in your ARN

Save the changes, and press the Deploy button.

Now click on the Test tab and then click the Test button.

Hopefully it works, and you see the response object from the DynamoDB.

Now you can look in your DynamoDB and see the new entry in the myFirstTable table.

In the AWS console, navigate to DynamoDB, then click on Tables (on the left). Then click on your table name (mine is myFirstTable).

Next click the Explore table items button on the right. Then click the Run button to look at all the items in the table.

Now, finally, we can connect the website we uploaded to Amplify to our lambda function!

In the index.html file that we created way back in step 1 you need to make POST requests to your API url, which we saved in a previous step.

You can simply console log the result return

Here's an example of using fetch() to set up the request:

var headers = new Headers();
headers.append("Content-Type", "application/json");
var body = JSON.stringify({"base":base,"exponent":exponent});

var requestOptions = {
    method: 'POST',
    headers: headers,
    body: body,
    redirect: 'follow'
};

fetch("YOUR API GATEWAY ENDPOINT", requestOptions)
  .then(response => response.text())
  .then(result => alert(JSON.parse(result).body))
  .catch(error => console.log('error', error));

Once you update the index.html file, then zip it and re-upload to Amplify To see that, go to 23:20 of the video (whenever you reupload the zip file, it should automatically re deploy it)

MAKE SURE YOU DELETE EVERYTHING WHEN YOU ARE DONE SO THAT YOU DON'T GET BILLED (see 24:45 of the video)

  1. delete the Amplify app
  2. delete the DynamoDb table
  3. delete your lambda function
  4. delete your API gateway
  5. delete the roles you created for your lambda functions (in IAM)