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