Lambda AWS Example | DynamoDB, API Gateway S3 FullStack App

December 12, 2018

Related image

Lambda AWS Example | DynamoDB, API Gateway S3 FullStack App

You’ve probably heard of the term full stack at some point or other and wondered what in the world it could possibly mean. Contrary to what initially comes to mind,the term has nothing to do with the data structure. A stack refers to a system composed of many technologies working together to produce a desired behavior.

To elaborate, suppose we had built a React based web application that enabled a user to add tasks to a to do list.

If we were to refresh the page, all the tasks added to the to do list would be lost.

For any client side application, if we want the data to persist beyond the user’s session, we must make use of some kind of backend. By storing the data inside of a database on a remote server and retrieving it once the application launches, users can safely refresh the page and/or close the browser.

LAMP although on its way out, is a common example of a full stack application. LAMP stands for:

  • Linux
  • Apache
  • MySQL
  • PHP

Today, you will most likely see some variation of a MEAN stack.

  • MongoDB
  • Express
  • Angular
  • Node

In proceeding article, we’ll take a look at how we can create a full stack application using React, Node (Lambda), DynamoDB and API Gateway.

We’ll start off by creating a database that will be used to store our list of tasks.

aws dynamodb create-table --table-name ToDoList --attribute-definitions AttributeName=Id,AttributeType=S AttributeName=Task,AttributeType=S --key-schema AttributeName=Id,KeyType=HASH AttributeName=Task,KeyType=RANGE --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5

I’d like to take this opportunity to clarify the function of roles in AWS. A role specifies what service or user you want to affect. It’s only after you attach policies to the role that it limits or grants access to some resource.

In the proceeding steps we will create a role that allows Lambda to write to and scan our DyanmoDB table.

aws iam create-role --role-name lambda-role --assume-role-policy-document file://trusted_entity.json
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
        "Service": [
          "lambda.amazonaws.com"
        ]
        },
        "Action": "sts:AssumeRole"
      }
    ]
}
aws iam put-role-policy --role-name lambda-role --policy-name dynamodb-access --policy-document file://set_policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:Scan"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:084696551378:table/ToDoList"
        }
    ]
}

Next, we’ll create two Lambda functions. The first will be used to scan the table for a list of task and the second will be used to add new tasks to the database. We specify the name to our table as an environment variable and import the DocumentClient to run queries against the DynamoDB instance.

const AWS = require('aws-sdk');
const documentClient = new AWS.DynamoDB.DocumentClient(); 

exports.getAllTasks = function(event, context, callback){
	const params = {
		TableName : process.env.TABLE_NAME
	};
	documentClient.scan(params, function(err, data){
		if(err){
		    callback(err, null);
		}else{
		    callback(null, data.Items);
		}
	});
}

Lambda functions must be uploaded in .zip format in the event you want to use external libraries.

zip function.zip get.js
`aws lambda create-function --function-name get-tasks --zip-file fileb://function.zip --runtime nodejs8.10 --role` arn:aws:iam::084696551378:role/lambda-role --handler get.getAllTasks --environment Variables={TABLE_NAME=ToDoList}
const AWS = require('aws-sdk');
const uuid = require('uuid');
const documentClient = new AWS.DynamoDB.DocumentClient(); 


exports.addTask = function(event, context, callback){
	var params = {
		Item : {
			"Id" : uuid.v1(),
			"Task" : event.task
		},
		TableName : process.env.TABLE_NAME
	};
	documentClient.put(params, function(err, data){
		callback(err, data);
	});
}
zip function2.zip post.js
`aws lambda create-function --function-name add-task --zip-file fileb://function2.zip --runtime nodejs8.10 --role` arn:aws:iam::084696551378:role/lambda-role --handler post.addTask --environment Variables={TABLE_NAME=ToDoList}

API Gateway gives us a way of executing the Lambda functions from the client. For you newbies out there, API stands for Application Programming Interface. As the name implies, an API is an endpoint (i.e. a URL) which is accessed by application code. Whenever you type URL to a web page, you are in fact requesting a collection of HTML, CSS and Javascript files from a server. These files are then interpreted by the browser in such a way as to make them more human readable. Servers can and do serve other forms of data the most notable of which is JSON. For example, you might design a hockey pool application that makes an API call in order to retrieve the latest statistics. Rather than request an entire web page, we can get the all information we want from a JSON object.

aws apigateway create-rest-api --name 'To Do List'

We need the resource ID associated with the root route (/).

aws apigateway get-resources --rest-api-id vaz7da96z6

In this example, we’ll be using the same route for scanning and writing to the database. However, in most real life scenarios, you’d use multiple routes in accordance with REST.

aws apigateway create-resource --rest-api-id vaz7da96z6 --parent-id begaltmsm8 --path-part tasks

Without delving into too much detail, REpresentational State Transfer or REST describes how a backend server and client should communicate. In essence, we don’t want them tightly linked. A backend server should in theory be able to be used by multiple applications. For example, in following the REST paradigm you might have a server linked to the cutepuppies.com domain with the following routes.

Every HTTP request is associated with a particular method. Whenever you vist a web page, you are in fact instantiating a request with the GET method. For our API, whenever we receive a GET request, we will return the list of tasks from our table. On the other hand, POST requests are used whenever you want to send data to the remote server. We will leverage POST requests to send and store tasks into our database.

aws apigateway put-method --rest-api-id vaz7da96z6 --resource-id 6sxz2j --http-method GET --authorization-type NONE
aws apigateway put-method --rest-api-id vaz7da96z6 --resource-id 6sxz2j --http-method POST --authorization-type NONE

By convention, whenever a HTTP request to a remote server is successful, it will respond with a 200 status code.

aws apigateway put-method-response --rest-api-id m9r6tlx76g --resource-id qhxwgr --http-method GET --status-code 200
aws apigateway put-method-response --rest-api-id m9r6tlx76g --resource-id qhxwgr --http-method POST --status-code 200

Although the CLI has the ability to map endpoints to Lambda functions, it’s much simpler to go through the console.

For the services to work together correctly, we must add responses to the integrations.

aws apigateway put-integration-response --rest-api-id m9r6tlx76g --resource-id qhxwgr --http-method GET --status-code 200 --selection-pattern ""
aws apigateway put-integration-response --rest-api-id m9r6tlx76g --resource-id qhxwgr --http-method POST --status-code 200 --selection-pattern ""

We won’t be able to make an API call from our application, unless we enable Cross-Origin Resource Sharing (CORS). CORS is a mechanism that uses additional HTTP headers to tell a browser to let a web application have permission to access selected resources from a server at a different origin.

Once CORS is enabled, we can deploy the API.

aws apigateway create-deployment --rest-api-id vaz7da96z6 --stage-name api

Unless modified using CloudFront, the URL to the API will have the following format.

..amazonaws.com//

Let’s test the endpoint by running curl.

curl -X GET https://m9r6tlx76g.execute-api.us-east-1.amazonaws.com/api/tasks
curl -X POST -d ‘{“task”:”Eat”}’ https://m9r6tlx76g.execute-api.us-east-1.amazonaws.com/api/tasks

If you are on Windows you can use the Powershell command Invoke-WebRequest or install an Postman.

Now we’re ready to start writing code for our front end. We’ll use the create-react-app library to get started without having to configure Webpack and Babel.

create-react-app full-stack

Update the App.js file, making sure you update the fetch statement to include the URL for your API.

import React, { Component } from 'react';

class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            item: '',
            tasks: []
        };
        this.handleSubmit = this.handleSubmit.bind(this);
        this.handleChange = this.handleChange.bind(this);
    }

    componentDidMount() {
        fetch('')
        .then(res => res.json())
        .then(json => {
            const tasks = json.map(item => item.Task)
            this.setState({ tasks: tasks })
        });
    }

    handleSubmit(e) {
        e.preventDefault();
        const { item, tasks } = this.state;

        fetch('', {
            method: 'POST',
            headers: {
              'Accept': 'application/json',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({task: item})
        });

        this.setState({
            item: '',
            tasks: [...tasks, item]
        });
        e.target.reset();
    }

    handleChange(e) {
        this.setState({
            item: e.target.value
        });
    }

    render() {
        const { tasks } = this.state;
        const todos = tasks.map((value, index) => *   { value }
);

        return (
            

                

                    Task
                    
                    Enter a todo
                

                

{ todos }

            

        );
    }
}

export default App;

Run the build command to bundle and minify our Javascript code.

npm run build

Create an S3 bucket and copy the build directory over to our bucket.

aws s3 mb s3://full-stack-app.corymaklin.com
aws s3 cp --recursive build s3://full-stack-app.corymaklin.com

By default, S3 buckets are private. Unless we update the policy to give users access, they won’t be able to access the website. We’ll create a .json file with the following contents. Make sure you replace the ARN for the one associated with your bucket.

aws s3api put-bucket-policy --bucket s3://full-stack-app.corymaklin.com --policy file://policy.json
 policy.json · GitHub
    
    
  
  

    
    

  
  
  
  
  

  

  

      
    
    

  

    

    

  
  
  

      
    

      
    

    

  

  
  

      

  

  

  

  
  

  

  
    

  

    [Skip to content](#start-of-content)
    

    
    
    


        

  

  

      

  

    
  

    

  [All gists](/discover)

  [Back to GitHub](https://github.com)

  

    

      [
        Sign in
](https://gist.github.com/auth/github?return_to=https%3A%2F%2Fgist.github.com%2Fcorymaklin%2F4b55257b25467b188e9a824c05241f43)        [
          Sign up
](/join?source=header-gist)    

  

  

    

  

        

    

      


  

    

      

        Instantly share code, notes, and snippets.
      

    

  

  

    
  

  

    

  [Code](/corymaklin/4b55257b25467b188e9a824c05241f43) 
    [Revisions
      1](/corymaklin/4b55257b25467b188e9a824c05241f43/revisions) 

  

  

    

  

    

      Embed 
        What would you like to do?
          

          

              
                
                

                  Embed 
                    Embed this gist in your website. 
                

              
                
                

                  Share 
                    Copy sharable link for this gist. 
                

              
                
                

                  Clone via
                    HTTPS 
                    Clone with Git or checkout with SVN using the repository’s web address. 
                

          

          

            [Learn more about clone URLs](https://help.github.com/articles/which-remote-url-should-i-use) 
    

    

    

  

    

    

      [Download ZIP](/corymaklin/4b55257b25467b188e9a824c05241f43/archive/2a7c94ea6ed261239f373cec74aa720f30054626.zip)
    

  

  

  

    
  

    

        

  

      

        

          [Raw](/corymaklin/4b55257b25467b188e9a824c05241f43/raw/2a7c94ea6ed261239f373cec74aa720f30054626/policy.json)
        

        

           
          [**policy.json**](#file-policy-json) 
        

      

    

  

      

      

        

        

{

      

      

        

        

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

      

      

        

        

    "Statement":[

      

      

        

        

        {

      

      

        

        

            "Sid":"PublicReadForGetBucketObjects",

      

      

        

        

            "Effect":"Allow",

      

      

        

        

            "Principal": "*",

      

      

        

        

            "Action":["s3:GetObject"],

      

      

        

        

            "Resource":["arn:aws:s3:::s3-tutorial-youtube.corymaklin.com/*"]

      

      

        

        

        }

      

      

        

        

    ]

      

      

        

        

 }

      

  

  

    
    

      

        

        

            

    [Sign up for free](/join?source=comment-gist)
    **to join this conversation on GitHub**.
    Already have an account?
    [Sign in to comment](/login?return_to=https%3A%2F%2Fgist.github.com%2Fcorymaklin%2F4b55257b25467b188e9a824c05241f43)

        

      

    

  

  

    

  

  

        

  

    

      *   © 2019 GitHub, Inc.
        *   [Terms](https://github.com/site/terms)
        *   [Privacy](https://github.com/site/privacy)
        *   [Security](https://github.com/security)
        *   [Status](https://githubstatus.com/)
        *   [Help](https://help.github.com)
    

    [](https://github.com "GitHub") 
   

        *   [Contact GitHub](https://github.com/contact)
        *   [Pricing](https://github.com/pricing)
      *   [API](https://developer.github.com)
      *   [Training](https://training.github.com)
        *   [Blog](https://github.blog)
        *   [About](https://github.com/about)

    

  

  

  

    
     
    You can’t perform that action at this time.
  

    
    
    
    
    
      

    
  

    
    You signed in with another tab or window. Reload to refresh your session.
    You signed out in another tab or window. Reload to refresh your session. 

Login to the console if you haven’t already. Navigate to your bucket then select static website hosting under the properties tab.

Visit the webpage, add some content and try refreshing the page. You should see the to do list persist beyond the session.

Note: If you run in to any errors, try redeploying the API.

Clap it! Share it! Follow Me!

For video tutorials, check out my youtube channel.

Cory Maklin
_Sign in now to see your channels and recommendations!_www.youtube.com


Profile picture

Written by Cory Maklin Genius is making complex ideas simple, not making simple ideas complex - Albert Einstein You should follow them on Twitter