myDynamoDB in react

By | December 13, 2019

My first react project is available online at https://mydynamodb.com.

MyDynamoDB is a social media app that saves github users to a database using API gateway, lambda functions, and a dynamoDB database. The user data comes from github using the github API.

Watch the video about it here

Goals for the project

My main objective in creating this project was to see if I could use dynamoDB as an alternative to MongoDB.

The second objective was to make my first react app.

DynamoDB vs mongoDB

DynamoDB consists of tables which are similar to collections in mongoDB.

Each of these tables consist of items which are similar to documents in mongoDB.

Primary keys are used as the main item ID in DynamoDB . MongoDB is better in this regard because it creates unique ids automatically vs. the developer needing to create unique primary keys for dynamoDB.

Database design for myDynamoDB.com

The database design for myDynamoDB.com is very simple it consists of a primary key named team_id, and two string data keys team_name and team_data. team_data holds a JSON string that contains the data object for the team members selected from github. DynamoDB also can have an optional sort key which I did not use for this project.

My opinions on dynamoDB

When I started this project I was not sure DynamoDB would be a good replacement for MongoDB.

After completing this project I am now sure it could be used as a replacement for apps that read more data than it write data.

Other things you need to consider are that dynamoDB writes slower than reads and writes are much more more expensive.

If your application is a chat app with equal reads and writes you should consider something different. If your application mostly reads data it would be a good choice.

The API

Originally I was going to write my own RESTful service to connect to dynamoDB.

After discussing this with some developers they suggested I try a lambda function instead. I found a boiler plate from AWS for dynamoDB when I set up my lambda function. It has only one header entry ‘Content-Type’: ‘application/json’. If you want CORS you need to add following headers to your lambda function.

'Access-Control-Allow-Origin': '*',,
'Access-Control-Allow-Methods': 'GET,HEAD,OPTIONS,POST,PUT',
'Access-Control-Allow-Headers': 'Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers',

Still even after this is it did not work. Most browsers send out an OPTIONS request to test CORS so your server code has to return success for OPTIONS. Then in my react app I had to add one last header as follows.

headers: {Accept: '*/*',}

In this app I use POST with a JSON body for all dynamoDB methods. The following JSON object is an example for dynamo.putItem.

{
   myBody: {
     TableName: TableName,
     Item: {
       team_id: team_id,
       team_name: team_name,
       team_data: team_data,
     },
     ReturnConsumedCapacity: 'TOTAL',
   },
   myMethod: 'putItem',
},

The API uses POST for security reasons because the query strings in a URL can be logged on the server. Plus it is nice to use a JSON body for all requests since it is easy to convert your class, data, etc to a JSON string. myMethod defines what dynamoDB function to call.

The following is the lambda function with POST being used for all calls.

console.log('Loading function');

const doc = require('dynamodb-doc');

const dynamo = new doc.DynamoDB();

/**
 * Demonstrates a simple HTTP endpoint using API Gateway.
 *
 * I use POST with a request body for all methods.
 * myMethod is used to define the method.
 * I like to do this for security reasons because the query strings
 * in URL can be logged on the server.
 *
 * The following JSON object is an example for dynamo.putItem.
 *
 *        {
 *          myBody: {
 *            TableName: TableName,
 *            Item: {
 *               team_id: team_id,
 *               team_name: team_name,
 *               team_data: team_data,
 *             },
 *             ReturnConsumedCapacity: 'TOTAL',
 *           },
 *          myMethod: 'putItem',
 *        }
 *
 * I also had to add the following get is to work with CORS
 * 'Access-Control-Allow-Origin': '*',
 * 'Access-Control-Allow-Methods': "GET,HEAD,OPTIONS,POST,PUT",
 * 'Access-Control-Allow-Headers': 'Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers'
 *
 */

exports.handler = (event, context, callback) => {
  const done = (err, res) =>
    callback(null, {
      statusCode: err ? '400' : '200',
      body: err ? err.message : JSON.stringify(res),
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET,HEAD,OPTIONS,POST,PUT',
        'Access-Control-Allow-Headers':
          'Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers',
      },
    });

  const lowestTeamNumber = 10; // items 0 to 10 protected from writing, for the demo app
  switch (event.httpMethod) {
    case 'OPTIONS':
      const response = {
        statusCode: 200,
        body: 'CORS check passed safe to proceed',
      };
      done(null, response);
      break;
    case 'POST':
      var myEventBody = JSON.parse(event.body);
      switch (myEventBody.myMethod) {
        case 'deleteItem':
          if (myEventBody.myBody.Key.team_id <= lowestTeamNumber) {
            done(new Error(`The team number is not in a valid range.  `));
          } else {
            dynamo.deleteItem(myEventBody.myBody, done);
          }
          break;
        case 'putItem':
          if (myEventBody.myBody.Item.team_id <= lowestTeamNumber) {
            done(new Error(`The team number is not in a valid range.  `));
          } else {
            dynamo.putItem(myEventBody.myBody, done);
          }
          break;
        case 'updateItem':
          if (myEventBody.myBody.Item.team_id <= lowestTeamNumber) {
            done(new Error(`The team number is not in a valid range.  `));
          } else {
            dynamo.updateItem(myEventBody.myBody, done);
          }
          break;
        case 'getItem':
          dynamo.getItem(myEventBody.myBody, done);
          break;
        case 'scan':
          dynamo.scan(myEventBody.myBody, done);
          break;
        default:
          done(new Error(`Unsupported method "${event.httpMethod}"`));
      }
      break;
    default:
      done(new Error(`Unsupported method "${event.httpMethod}"`));
  }
};

All this code is available on my git hub account which you can find here my-dynamodb-demo .