An in-depth comparison of the most popular DynamoDB wrappers

thomasaribart

Thomas Aribart

Posted on March 21, 2023

An in-depth comparison of the most popular DynamoDB wrappers

AWS DynamoDB is a key-value database designed to run high-performance applications at any scale. It automatically scales up and down based on your current traffic, and does not require maintaining connections (as requests are sent over HTTP), which makes it the go-to DB service for serverless developers on AWS.

Because it’s 2023 and no-one writes HTTP requests anymore, AWS published a SDK called the document client to craft said requests. However, if you’ve ever used it, you will know that it’s still very painful to use.

For instance, let’s look at this UpdateCommand example from the DynamoDB docs itself:



await DocumentClient.send(
  new UpdateCommand({
    TableName: 'TABLE_NAME',
    Key: {
      // 👇 No type-safety on the primary key
      title: 'MOVIE_NAME',
      year: 'MOVIE_YEAR',
    },
    // 👇 String expressions hard to build (and still no type-safety)
    UpdateExpression: 'set info.plot = :p, info.#r = :r',
    // 👇 When used in Expressions, attribute names have to be provided separately
    ExpressionAttributeNames: {
      '#r': 'rank',
    },
    // 🤦‍♂️ List of attribute names as strings separated by commas
    ProjectionExpression: '#r',
    // 👇 Attribute values have to be provided separately
    ExpressionAttributeValues: {
      // 👇 No validation or type-safety to enforce DB schema
      ':p': 'MOVIE_PLOT',
      ':r': 'MOVIE_RANK',
    },
  }),
);


Enter fullscreen mode Exit fullscreen mode

It is a very simple example (updating two fields of a Movie item), yet already very verbose 😅 And things only get messier as the complexity of your business logic grows: What if your items have 20 attributes? With some of them deeply nested? Or optional? What if you want to index an item or not depending on its values (e.g. a status attribute)? What about polymorphism?

In those cases (which are quite common) the required code to generate those requests can get very hard to maintain. That is why, very early on, developers built open-source libraries to “wrap” the DynamoDB client, with two goals in mind:

  • 🏋️‍♀️ Simplifying the writing of DynamoDB requests
  • 📐 Adding run-time data validation, i.e. “artificial” schemas to a schema-less DB (and more recently type-safety)

For instance, here is an example of the same UpdateCommand with one of those wrappers, DynamoDB-Toolbox:



import { Table, Entity } from 'dynamodb-toolbox';

// Provided some schema specifications...
const MovieTable = new Table({
  name: 'TABLE_NAME',
  partitionKey: 'title',
  sortKey: 'year',
  DocumentClient, // <= the original DocumentClient
});

const MovieEntity = new Entity({
  name: 'Customer',
  attributes: {
    title: { partitionKey: true, type: 'string' },
    year: { sortKey: true, type: 'string' },
    info: { type: 'map' },
  },
  table: MovieTable,
} as const);

// ...we get a validated AND type-safe request method 🙌
await MovieEntity.update({
  title: 'MOVIE_NAME',
  year: 'MOVIE_YEAR',
  info: {
    plot: 'MOVIE_PLOT',
    rank: 'MOVIE_RANK',
  },
});


Enter fullscreen mode Exit fullscreen mode

And just like that, we went from an obscure 18-line object to a readable and elegant 8-liner 🤩 Not bad, don't you think?

DynamoDB-Toolbox is not the only SDK wrapper out there. If you browse Alex DeBrie’s awesome-dynamodb-tools repo, you’ll actually find a bunch of them. So, which one should you chose?

In this article, we took an in depth comparison of the 4 most popular DynamoDB wrappers:

  • 🦌 Dynamoose: Dynamoose is the OG of DynamoDB wrappers. Created in 2015, it provides a syntax that closely mirrors that of the popular Mongoose library for MongoDB.
  • 🧰 DynamoDB Toolbox: DynamoDB-Toolbox was created by Jeremy Daly, AWS Serverless Hero, and writer of the newsletter off-by-none. It was first released in September 2018 and has gained in popularity ever since.
  • ⚡️ ElectroDB: Released in April 2020 by Tyler W. Walsh, ElectroDB benefits from having been created after Typescript won over the JS ecosystem. So it has a higher focus on type-safety than its predecessors.
  • 💍 DynamoDB-OneTable: First released in January 2021, DynamoDB-OneTable is maintained by Sensedeep and is part of its broader Serverless Developer Studio offer.

We ranked them based on the following criteria:

  • 📣 Library state: Classic open-source KPIs such as number of downloads, community, documentation etc.
  • 🏗️ Data modeling: The broadness of their Entity definition API. Do they allow attribute name re-mapping (useful for single-table design)? Do they support enums? Nested attributes definitions? Computing indexes from other attributes?
  • ✨ Typescript support: Type-safety is all the rage these days! All libraries come with some sort of type-safety, but type-inference (i.e. inferring tailored types from custom schemas) is still hard to get right.
  • 🤖 API: How easy it is to do common DynamoDB requests like put, get or query... with secondary indexes, filters and conditions (You can find examples for each wrapper in our dedicated repo).

📣 Library state

(*as of 2023/02) 🦌 Dynamoose 🧰 DynamoDB-Toolbox ⚡️ ElectroDB 💍 DynamoDB-OneTable
First release date 2014-02-27 2019-11-20 2020-03-11 2021-01-12
Last release date ✅ Jan. 6, 2023 ✅ Jan. 8, 2023 ✅ Jan. 20, 2023 ✅ Jan. 25, 2023
Github ⭐️ 1900 1400 530 505
NPM weekly downloads 86 k 38 k 4 k 16 k
Bundle size 🟡 382 kB ✅ 64.1 kB 🟡 176.7 kB ✅ 64.3 kB
Documentation 🟡 No global search ❌ Can be improved
DynamoDB Client v3 compatibility

All four libraries are well maintained, and have enough GitHub stars and npm downloads to be considered “battle tested”.

Dynamoose has the highest stats (probably from being the first one around). However, it’s the heaviest, with a size of 382KB, which is not negligible, considering that bundles above 5MB negatively impact Lambdas cold starts.

DynamoDB Wrappers Stars History

The main takeaways are that DynamoDB-Toolbox is not compatible with the V3 of the DynamoDB client (though it should be coming soon), and that the DynamoDB-OneTable documentation leaves to be desired.


🏗️ Data modeling

We initially started with a very broad scope of features useful for Entity definition (like specifying attributes as required, or aliasing attributes). However, most of them were already implemented by all libraries. For the sake of simplicity, we removed them and kept the following ones:

  • Nested attributes definition: Could we type nested fields of lists and maps attributes (like plot and rank in our first Movie example)?
  • Enum support: Could we specify a finite range of values for a primitive attribute?
  • Default values: Could we provide default values for an attribute? That is especially useful for entities with “simple” access patterns like fixed strings. We differentiated independent defaults (fixed or derived from context such as timestamps or env variables) from dependent defaults (computed from other attributes).
  • Pre-save/post-fetch attribute transformation: This can be needed for technical reasons, such as prefixing attributes. When possible, it’s best to hide such details from your code and let your wrapper handle the heavy-lifting.
  • Polymorphism support: Sometimes, items can switch between different statuses and shapes that go with them. We tested how easy it was to translate to in each library.
🦌 Dynamoose 🧰 DynamoDB-Toolbox ⚡️ ElectroDB 💍 DynamoDB-OneTable
Nested attributes definition
Enum support
Independent defaults
Dependent defaults 🟡 Via the set option 🟡 Via string templates like "user#${email}"
Attribute value transformation
Polymorphism

Overall, Dynamoose and ElectroDB have the upper hand, with ElectroDB being slightly ahead as it allows deriving attributes default values from other attributes.

Surprisingly, none of those libraries handles polymorphism and type-safe dependent defaults. As a maintainer of DynamoDB-Toolbox, I know for sure that those features are coming in the next major, so if you’re already using it, do not consider migrating to ElectroDB just yet 🙂


✨ Typescript support

All libraries support Typescript at a basic level, so we mostly focused on type inference. We looked for type inference in:

  • DynamoDB requests: Root and nested level attributes
  • Dependent defaults definition
  • Expressions: Conditions, filters and projections
🦌 Dynamoose 🧰 DynamoDB-Toolbox ⚡️ ElectroDB 💍 DynamoDB-OneTable
Requests (Root attributes)
Requests (Nested attributes)
Dependent defaults
Expressions 🟡 yes but only at root level ❌ Via string templates like "(${role} = {admin})"
IDE performances ❌ Slow

Once again ElectroDB has the upper hand here. Very nice job, Tyler W. Walsh 😎


🤖 API

Finally we compared the APIs of each solution for sending DynamoDB requests, i.e. how easy they were to write and understand (verbosity, complexity etc).

  • Single Item Requests: For usual PUT, GET and DELETE requests.
  • Updates: For UPDATE requests. We tested updating nested fields, adding values to numbers and elements to lists.
  • Queries: For querying and using secondary indexes.
  • Conditions & filters: For conditioned write operations and filtered query results.
🦌 Dynamoose 🧰 DynamoDB-Toolbox ⚡️ ElectroDB 💍 DynamoDB-OneTable
Single Item Requests 🟡 Clunky 🟡 Hard to override an existing item (PUT)
Updates 🟡 Clunky 🟡 Operations ($delete, $add...) mixed with attributes 🟡 Operations (delete, add...) are far from attributes (in options)
Queries ❌ Uses only FilterExpression 🟡 Confusing query formalism
Conditions & filters 🟡 Verbose, ORM-ish 🟡 Confusing OR clauses 🟡 Easy to use but incomplete (e.g. AND/OR clauses) ❌ String template 🙄

If the previous section wasn't enough, this one should convince you NOT to use Dynamoose: I found it hard to believe, but it doesn't even support basic (efficient) queries, and uses only filter expressions under the hood 🙄.

That being said, ElectroDB has a the nicest API. I especially liked the possibility to name queries according to their business meaning, but not the find and match methods, which are not native DynamoDB requests and can build costly and inefficient scan requests without you being aware.


Conclusion

Overall, as of march 2023, ElectroDB looks like the best DynamoDB client wrapper. Although it is newer and less “battle tested”, it is better than its concurrent on every other criteria: It has the same data modeling features, a more complete type inference, and a nicer API.

Nevertheless, there are some parts that could be improved:

  • Type inference still has some blind spots
  • There's no support for polymorphism
  • Entity definition autocompletion could be more helpful (it would benefit from a zod-like approach)

Finally, I would not rule out DynamoDB-Toolbox just yet! Its next major is just around the corner, with many new capabilities that even ElectroDB doesn’t have (such as type-safe dependent defaults and polymorphism). So expect a round 2 of this article in the next few months… 😉

💖 💪 🙅 🚩
thomasaribart
Thomas Aribart

Posted on March 21, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related