S
Sep 13, 2025
4 min read

Introducing DynaORM: A Type-Safe DynamoDB ORM for TypeScript Developers

Working directly with DynamoDB can be intimidating. The AWS SDK is powerful, but it often feels too low-level for everyday development. You spend too much time writing boilerplate, validating data, and worrying about missing type safety.

That’s why I built DynaORM - a simple, type-safe DynamoDB ORM powered by TypeScript and Zod.

DynaORM makes it easy to define schemas, work with strongly typed models, and perform CRUD operations without sacrificing flexibility or control. Whether you’re building a small side project or a large-scale system, DynaORM helps you move faster with confidence.


Why Another ORM for DynamoDB?

If you’ve ever worked with DynamoDB in TypeScript, you’ve probably run into these issues:

  • Type Safety Gaps: The AWS SDK doesn’t enforce strict typing on your data. You end up with runtime surprises.

  • Manual Validation: Ensuring your objects conform to expected shapes usually requires custom validation logic.

  • Boilerplate Overload: CRUD operations, batch writes, and queries often mean rewriting the same code again and again.

  • Complex Querying: Building queries against indexes requires a lot of string concatenation and DynamoDB-specific quirks.

DynaORM solves these pain points by combining the strengths of TypeScript (types), Zod (validation), and a clean ORM-like API.


🚀 Key Features

  • End-to-End Type Safety – Infer types directly from Zod schemas. No more mismatches between your models and database.

  • Schema Validation – Zod ensures your data is always valid before writing to DynamoDB.

  • Clean CRUD API – Simple methods like create, findOne, update, and delete.

  • QueryBuilder – Build expressive, chainable queries for tables and indexes.

  • Batch Operations – Perform batch gets, writes, and upserts with minimal code.

  • Configurable Throttling – Apply per-model or global rate limits to keep AWS happy.


📦 Getting Started

Install DynaORM and its peer dependencies:

npm install dynaorm @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb p-throttle zod

Step 1: Define Your Schema

Schemas are defined using Zod and wrapped with DynaORM’s defineSchema.

import { defineSchema } from "dynaorm";
import { z } from "zod";

export const userSchema = defineSchema({
  tableName: "Users",
  partitionKey: "userId",
  sortKey: "createdAt",
  fields: z.object({
    userId: z.string().uuid(),
    createdAt: z.string().datetime(),
    email: z.string().email(),
    username: z.string().min(3).max(20),
    age: z.number().optional(),
  }),
});

Type inference comes built-in:

import { InferSchema } from "dynaorm";
import { userSchema } from "./schemas/user-schema";

export type User = InferSchema<typeof userSchema>;

const newUser: User = {
  userId: "c44a8f2d-7f7d-421b-8f4e-7a1c0a9e6df9",
  createdAt: new Date().toISOString(),
  email: "[email protected]",
  username: "exampleUser",
};

Step 2: Create the Client

You pass your schemas into createClient along with AWS config and optional throttling rules.

import { createClient } from "dynaorm";
import { DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";

import { userSchema } from "./schemas/user-schema";

const clientConfig: DynamoDBClientConfig = {
  region: "ap-southeast-2",
};

export const client = createClient({ users: userSchema }, { config: clientConfig });

Step 3: Perform Operations

CRUD is straightforward and always type-safe:

import { client } from "./client";

async function main() {
  // Create
  await client.users.create({
    userId: "123",
    createdAt: new Date().toISOString(),
    email: "[email protected]",
    username: "newUser",
  });

  // Read
  const user = await client.users.findOne({ userId: "123", createdAt: "..." });
  console.log("User:", user);

  // Update
  await client.users.update({ userId: "123", createdAt: "..." }, { age: 30 });

  // Delete
  await client.users.delete({ userId: "123", createdAt: "..." });
}

⚡ Advanced Usage

Queries Made Simple

const recentUsers = await client.users
  .query()
  .where("userId", "=", "123")
  .where("createdAt", ">", "2024-01-01T00:00:00Z")
  .exec();

Batch Operations

await client.users.upsertMany([
  { userId: "u1", createdAt: "2024-09-01T00:00:00Z", email: "[email protected]", username: "alpha" },
  { userId: "u2", createdAt: "2024-09-02T00:00:00Z", email: "[email protected]", username: "beta" },
]);

Index Support

const electronics = await client.products.findByIndex("byCategory", {
  category: "electronics",
});

🛠 When Should You Use DynaORM?

  • ✅ You want strong typing without maintaining separate interfaces and schemas.

  • ✅ You’re tired of boilerplate DynamoDB queries.

  • ✅ You want an ORM-like abstraction without losing DynamoDB’s performance benefits.

  • ✅ You need safe queries and validation built-in.

If you need raw DynamoDB performance at all costs and prefer total manual control, stick with the AWS SDK. But if you value developer experience, DynaORM gives you the best of both worlds.