Using DynamoDB with TypeScript and OneTable
Tags: aws, serverless, dynamodb, nosql
Published: 2021-05-05 23:30:05

---
title: Using DynamoDB with TypeScript and OneTable
published: true
description: Using DynamoDB with TypeScript and OneTable
tags: aws, serverless, dynamodb, nosql
cover_image: https://www.sensedeep.com/images/blog/typescript.png
canonical_url: https://www.sensedeep.com/blog/posts/2021/dynamodb-typescript.html
---

TypeScript is a scripting language built on JavaScript that provides strong typing support to describe the types and object shapes used in your applications.

DynamoDB is increasingly being used with TypeScript where it provides strong API checks and guarantees. However, is natural to want TypeScript type support for both the API and the data entities that are passed to and from the database.

This post discusses how the [DynamoDB OneTable](https://www.sensedeep.com/blog/posts/2020/dynamodb-onetable.html) library uses dynamic TypeScript support fully type check DynamoDB data and achieve an elegant solution.

<!--BREAK-->
<!--more-->

## The Problem

DynamoDB is a NoSQL, key-value document database. As such, it imposes no schema on the data stored. This lack of a schema becomes increasingly apparent when using [Single Table](https://www.sensedeep.com/blog/posts/2021/dynamodb-singletable-design.html) design patterns where all (or most) application data entities are stored in a single DynamoDB table and attributes are overloaded with different data values for the various entities.

Developers using DynamoDB are increasingly turning to TypeScript as it provides them with strong guarantees regarding API correctness via type signatures for APIs. These TypeScript users also want the same type guarantees when reading and writing database data. However, how do you create type signatures for untyped data that is being stored in a NoSQL database with no schema?

[DynamoDB OneTable](https://www.sensedeep.com/blog/posts/2020/dynamodb-onetable.html) solves this issue and provides data typing for both APIs and database entities and attributes.

OneTable is an access library for [DynamoDB](https://aws.amazon.com/dynamodb/) applications that makes dealing with DynamoDB and single-table design patterns dramatically easier. OneTable provides TypeScript type declarations for the public API. However, this is just the start, because via TypeScript dynamic typing, OneTable creates new types automatically to validate database entities and attributes.

## Why a Schema?

TypeScript users may ask "Why use a Schema?" Why not just derive everything from a foundation of TypeScript types?

The answer is that while TypeScript type declarations do provide type checking and API guarantees, they do not further validate the data being written to database. Database data typically needs more constraints that just basic typing.

A OneTable schema provides definition and control over the following data attribute properties:

* Data type
* Data value range checking via regular expressions
* Whether the attribute is required when created
* Whether the attribute value is unique across other items
* A default value if not supplied by the caller
* A derived value based upon other attribute values

For example, here is a basic OneTable schema.

```javascript
const MySchema = {
    indexes: {
        primary: { hash: 'pk', sort: 'sk' },
    },
    models: {
        Account: {
            pk:          { type: String, value: 'account:${name}' },
            sk:          { type: String, value: 'account:' },
            id:          { type: String, uuid: true, 
                           validate: /^[0-9A-F]{32}$/i },
            name:        { type: String, required: true },
            status:      { type: String, default: 'active' },
        },
        User: {
            pk:          { type: String, 
                           value: 'account:${accountName}' },
            sk:          { type: String, 
                           value: 'user:${email}', 
                           validate: EmailRegExp },
            id:          { type: String, required: true },
            accountName: { type: String, required: true, 
                           unique: true },
            email:       { type: String, required: true },
            firstName:   { type: String, required: true },
            lastName:    { type: String, required: true },
            role:        { type: String, enum: ['user', 'admin'],
                           required: true, default: 'user' },
            balance:     { type: Number, default: 0 },
        }
    }
}
```

## TypeScript OneTable

Before explaining how OneTable generates dynamic types, let's see what it looks like.

To create a TypeScript type for an entity, we use the TypeScript `typeof` operator:

```typescript
type Account = Entity<typeof MySchema.models.Account>
```

The `Account` type will now fully validate property access and catch invalid references and data assumptions. For example:

```typescript
let account: Account = {
    name: 'Coyote',        //  OK
    unknown: 42,           //  Error
}
```

To access DynamoDB, we use a previously constructed `Table` object to get an access model object.

```typescript
//  Get an Account access model
let AccountModel: Model<Account> = table.getModel('Account')
```

The `AccountModel` provides access to the `find`, `get`, `remove`, `update` and other OneTable APIs to interact with DynamoDB.

```typescript
let account = await AccountModel.update({
    name: 'Acme',               //  OK
    unknown: 42,                //  Error
})

account.name = 'Coyote'         //  OK
account.unknown = 42            //  Error
```

## TypeScript Magic

The challenge in creating dynamic types involves tackling three key issues:

* Creating type declarations for each entity (MySchema.models.*)
* Creating type declarations for the attributes under each entity (MySchema.models.*.*)
* Interpret the schema attribute data type and create a corresponding TypeScript type

OneTable uses several TypeScript operators to dynamically create types from a OneTable schema.

These include:

* [Indexed access types](https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html)

* [Mapped types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html)

* [Keyof types](https://www.typescriptlang.org/docs/handbook/2/keyof-types.html)


### Indexed Access Types

To create type declarations for each model, we used TypeScript indexed access types. This dynamically creates type signatures for each model defined in the schema.

```typescript
type OneSchema = {
    models?: {
        [key: string]: OneModelSchema
    },
};
```

Similarly, the OneModelSchema creates type declarations for each attribute in the model.

```typescript
export type OneModelSchema = {
    [key: string]: OneFieldSchema
};
```


For each entity attribute, we sleuth the attribute `type:` property and define a type union that selects the appropriate data type for the attribute.

```typescript
type EntityField<T extends OneTypedField> =
      T['type'] extends StringConstructor ? string
    : T['type'] extends NumberConstructor ? number
    : T['type'] extends BooleanConstructor ? boolean
    : T['type'] extends ObjectConstructor ? object
    : T['type'] extends DateConstructor ? Date
    : T['type'] extends ArrayConstructor ? any[]
    : never;
```

Finally, we create a generic Entity type that is used to create entity types based on the schema.

```typescript
export type Entity<T extends OneTypedModel> = {
    [P in keyof T]?: EntityField<T[P]>
};
```

The end result of all this TypeScript magic is fully dynamic type declarations for your data entities and attributes based on a single source of truth: the OneTable schema.

Here is the complete OneTable Model.d.ts dynamic type definition file in GitHub:

[https://github.com/sensedeep/dynamodb-onetable/blob/main/src/Model.d.ts](https://github.com/sensedeep/dynamodb-onetable/blob/main/src/Model.d.ts)

## TypeScript and OneTable

By using dynamic TypeScript operators, OneTable is able to provide strong type guarantees for both its API and for your database entities and attributes. The net result is fewer errors, earlier detection of errors and faster serverless development.

## SenseDeep with OneTable

At SenseDeep, we've used OneTable and the OneTable CLI to create our [SenseDeep](https://app.sensedeep.com) serverless developer studio. All data is stored in a single DynamoDB table and we extensively use single-table design patterns. We could not be more satisfied with DynamoDB implementation. Our storage and database access costs are insanely low and access/response times are excellent.

Please try our Serverless developer studio [SenseDeep](https://www.sensedeep.com/).

### Contact

You can contact me (Michael O'Brien) on Twitter at: [@mobstream](https://twitter.com/mobstream), or [email](mob-pub-18@sensedeep.com) and ready my [Blog](https://www.sensedeep.com/blog).

To learn more about SenseDeep and how to use our serverless developer studio, please visit [https://www.sensedeep.com/](https://www.sensedeep.com/).

### References

* [Dynamic Static Typing](https://www.smashingmagazine.com/2021/01/dynamic-static-typing-typescript/)
* [TypeScript Advanced Typing](https://levelup.gitconnected.com/typescript-advanced-types-this-type-and-dynamic-types-ecb99c4ec275)
* [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html)

### Links

* [OneTable](https://www.sensedeep.com/blog/posts/2020/dynamodb-onetable.html)
* [OneTable Migrate CLI](https://www.npmjs.com/package/onetable-cli)
* [DynamoDB Checklist](https://www.sensedeep.com/blog/posts/2021/dynamodb-checklist.html)
* [DynamoDB Single Table Design](https://www.sensedeep.com/blog/posts/2021/dynamodb-singletable-design.html)
* [DynamoDB Sparse Indexes](https://www.sensedeep.com/blog/posts/2021/sparse-gsi-indexes.html)
* [OneTable Migrate CLI](https://www.npmjs.com/package/onetable-cli)
* [SenseDeep Web Site](https://www.sensedeep.com/)
* [SenseDeep App](https://app.sensedeep.com/)