Improving your S3 website with Cognito and DynamoDB

If you've already my previous posts on configuring Amazon S3 to host a static web application and configuring a CloudFront distribution to deliver application content and building a basic website with S3 and JavaScript and adding dynamic features to a basic S3 hosted website, then you're going to fit right in with this discussion which focuses on how to use Cognito and DynamoDB to super charge your website by including access to a database engine.

This article pulls heavily from this example repo. I recommend poking around in order to familiarize yourself with it's contents.

Setting up Cognito

In this example setting up Cognito is pretty straightforward. However it can become increasingly difficult if you're connecting several outside authentication providers to it.

  1. Start by opening the AWS Cognito console.
  2. Click "Create new identity pool". Or skip ahead if you already have one created.
  3. For the "Identity pool name" text box, give the identity pool a reasonable name. Eg. "justinfox".
  4. Under "Unauthenticated identities", check the box for "Enable access to unauthenticated identities". This will allow us to call to DynamoDB later without being logged in.
  5. Click "Create Pool".
  6. You'll be prompted to create two IAM roles - one for authenticated access and another for unauthenticated access. Name these as appropriate, and accept the defaults for now. We'll be customizing the unauthenticated role in a future step.
  7. You'll be dropped on a page with example code. For now click on the edit identity pool link in the top right.
  8. You'll see your "Identity pool ID" on this page. Make note of it. You'll need it when we get into code.

Setting up DynamoDB

Another easy setup.

  1. Start by opening the AWS DynamoDB console.
  2. Click "Create table".
  3. Specify a "Table name". Eg. "article_list".
  4. Specify a "Primary key". Eg. "article_id".
  5. Click "Create".

Enter in some sample data or import external json formatted data:

~/dynamodb-import.json:

{
    "article_list": [
        {
            "PutRequest": {
                "Item": {
                    "title"      : {"S": "Aggregated DNS Results for shorter DNS lookup times"},
                    "desc"       : {"S": ""},
                    "article_id" : {"N": "1"},
                    "limit"      : {"N": "600" },
                    "tags"       : {"SS": ["AWS", "Route53", "DNS"]},
                    "location"   : {"S": ".html"},
                    "date"       : {"S": "2015-01-11"}
                }
            }
        },
        ...
        {
            "PutRequest": {
                "Item": {
                    "title"      : {"S": "Using CloudFormation templates to implement network infrastructure"},
                    "desc"       : {"S": ""},
                    "article_id" : {"N": "25"},
                    "limit"      : {"N": "600" },
                    "tags"       : {"SS": ["AWS", "CloudFormation", "VPC"]},
                    "location"   : {"S": ".html"},
                    "date"       : {"S": "2015-02-22" }
                }
            }
        }
    ]
}

The command for importing:

aws dynamodb batch-write-item --request-items file://~/dynamodb-import.json

Implementation

I should start this off by saying that there are far better ways of doing this. Consider this a basic example only!

We'll start off by including the AWS JavaScript SDK with our website through modifying our index.html template to include it. This means that the SDK will be available on every page.

~/index.html:

...
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.2.37.min.js"></script>
...

In our ~/app/app.js file add this to the top:

~/app/app.js:

// Identity pool already configured to use roles
var creds = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: '[your id]'
});
AWS.config.update({
    region: '[region]',
    credentials: creds
});

That's it! You've integrated unauthenticated access to your website using Cognito! Now to actually do something with that... we're going to connect to DynamoDB! Back in your ~/app/app.js file...

~/app/app.js:

...
var dynamodb = new AWS.DynamoDB();
var params = {
    TableName: '[table name]',
    Select: 'ALL_ATTRIBUTES'
};
dynamodb.scan(params, function(err, data) {
    if (err) console.log(err, err.stack);
    else {
        console.log(data);
    }
});

The above snippet connects to DynamoDB using the generated Cognito credentials transparently. We then initiate a scan of our selected table which returns the complete table as a result. You can also run queries.

We can actually modify the articles controller from the example repo to use DynamoDB rather than list.json.

profileApp.controller('articlesCtrl', function ($scope, $location) {
    var dynamodb = new AWS.DynamoDB();
    var params = {
        TableName: 'article_list',
        Select: 'ALL_ATTRIBUTES'
    };
    dynamodb.scan(params, function(err, data) {
        if (err) console.log(err, err.stack);
        else {
            var yyyy = new Date().getFullYear().toString();
            var mm = (new Date().getMonth()+1).toString();       
            var dd  = new Date().getDate().toString();
            var today = yyyy + '-' + (mm[1]?mm:"0"+mm[0]) + '-' + (dd[1]?dd:"0"+dd[0]);
            $scope.today = today;
            var list = [];
            var flist = [];
            data.Items.forEach(function(article) {
                var item = {};
                item.title = article.title.S;
                item.tags = article.tags.SS.join(", ");
                item.location = article.location.S;
                item.date = article.date.S;
                if(item.date <= today){
                    list.push(item);
                }
                if(item.date > today){
                    flist.push(item);
                }
            });
            $scope.list = list;
            $scope.flist = flist;
            $scope.search = $location.search().search;
            $scope.$apply();
        }
    });
});