December 29, 2020

Creating Tables with DynamoDB Toolbox

I'm playing around with DynamoDB Toolbox, and one thing that wasn't clear in my mind was how do I actually create a table using the definitions I made using Toolbox.

Quick GitHub issues search lead me to this issue

where Simlu was asking the same question, and the answer was "Do It Yourself".

I dread code duplication especially around things like table definitions - it's trivial to have all your tests pass, but in the end, the app fails in production because you have a mismatch between two definitions of the same thing.

I've made a little example repo and a helper. For now, feel free to just copy and paste it.

A helper like this might get into the dynamodb-toolbox itself, so I didn't create a package for it yet. But if jeremydaly decides this code should live in a package, I will do that.


import type { DynamoDB } from "aws-sdk";

type DynamoDBTypes = "string" | "number" | "binary";

type ToolboxData = {
  partitionKey: string;
  sortKey?: string;
  attributes: {
    [key: string]: DynamoDBTypes;
  };
  name: string;
};

function getAttributes(tableDefinition: DynamoDB.CreateTableInput) {
  const typesMap: { [key: string]: DynamoDBTypes } = {
    S: "string",
    N: "number",
    B: "binary",
  };

  return tableDefinition.AttributeDefinitions.reduce((previous, current) => {
    return {
      ...previous,
      [current.AttributeName]: typesMap[current.AttributeType],
    };
  }, {});
}

function getPartitionKey(tableDefinition: DynamoDB.CreateTableInput) {
  return tableDefinition.KeySchema.find((k) => {
    return k.KeyType.toUpperCase() === "HASH";
  }).AttributeName;
}

function getSortKey(tableDefinition: DynamoDB.CreateTableInput) {
  const rangeAttribute = tableDefinition.KeySchema.find((k) => {
    return k.KeyType.toUpperCase() === "RANGE";
  });
  return rangeAttribute ? rangeAttribute.AttributeName : undefined;
}

export const dynamoSdkToToolbox = (
  tableDefinition: DynamoDB.CreateTableInput
): ToolboxData => ({
  partitionKey: getPartitionKey(tableDefinition),
  attributes: getAttributes(tableDefinition),
  sortKey: getSortKey(tableDefinition),
  name: tableDefinition.TableName,
});

(there are tests for it in the example repo)

This is how to use it. Assuming you have a table definition using AWS SDK format:


import type { DynamoDB } from "aws-sdk";

export const tableDefinition: DynamoDB.CreateTableInput = {
  TableName: "my-table",
  AttributeDefinitions: [
    {
      AttributeType: "S",
      AttributeName: "pk",
    },
    {
      AttributeType: "S",
      AttributeName: "sk",
    },
  ],
  KeySchema: [
    {
      AttributeName: "pk",
      KeyType: "HASH",
    },
    {
      AttributeName: "sk",
      KeyType: "RANGE",
    },
  ],
  BillingMode: "PAY_PER_REQUEST"
};

src/tableDefinition.ts

pass it to the dynamoSdkToToolbox helper:


const DocumentClient = new DynamoDB.DocumentClient()

const MyTable = new Table({
  ...dynamoSdkToToolbox(tableDefinition),
  DocumentClient
})

src/Customer.ts

This will set things like attributes, partitionKey, sortKey, and name for you - making sure they are and stay in sync.

Using the same table definition you can create a table (although you would probably use cloud formation or CDK for that, that's a topic for a separate article, but you might want to use a helper like this to transform the SDK definition to CDK https://gist.github.com/lgandecki/e7806462ce7c3d0d47ce65d44c5aa43d )


await dynamoDB.createTable(tableDefinition).promise()

src/index.ts

Feel free to play around with the code.

Let me know if you have any questions or thoughts in the comments below.

Keep reading