Getting Started
Get up and running with effect-dynamodb in five minutes. This guide walks you through setting up a local DynamoDB instance, defining a model, creating a table and entity, and performing basic CRUD operations.
Prerequisites
Section titled “Prerequisites”- Node.js 18+
- Docker installed and running
- Basic familiarity with Effect TS and Effect Schema
Running DynamoDB Local
Section titled “Running DynamoDB Local”For local development, use DynamoDB Local — a Docker image that runs a local instance of DynamoDB with no AWS account required.
Start a DynamoDB Local container:
docker run -d -p 8000:8000 --name dynamodb-local amazon/dynamodb-localThis starts DynamoDB Local on port 8000 in the background. The container persists until you stop it:
docker stop dynamodb-local # stopdocker start dynamodb-local # restart laterdocker rm dynamodb-local # remove when doneTo verify it’s running:
export AWS_ACCESS_KEY_ID=localexport AWS_SECRET_ACCESS_KEY=localaws dynamodb list-tables --endpoint-url http://localhost:8000 --region us-east-1Installation
Section titled “Installation”pnpm add effect-dynamodb effect @aws-sdk/client-dynamodb @aws-sdk/util-dynamodbQuick Start: A Single Entity
Section titled “Quick Start: A Single Entity”1. Define a Model
Section titled “1. Define a Model”Models are plain Effect Schema definitions — no DynamoDB concepts. Use Schema.Class for class instances with instanceof support, or Schema.Struct for plain objects.
import { Schema } from "effect"
class Todo extends Schema.Class<Todo>("Todo")({ todoId: Schema.String, title: Schema.NonEmptyString, done: Schema.Boolean,}) {}2. Create a Schema
Section titled “2. Create a Schema”import { DynamoSchema } from "effect-dynamodb"
const AppSchema = DynamoSchema.make({ name: "myapp", version: 1,})3. Define an Entity
Section titled “3. Define an Entity”The Entity definition connects your model to the table with key composition rules and optional features.
import { Entity } from "effect-dynamodb"
const TodoEntity = Entity.make({ model: Todo, entityType: "Todo", primaryKey: { pk: { field: "pk", composite: ["todoId"] }, sk: { field: "sk", composite: [] }, }, timestamps: true,})This tells effect-dynamodb:
- The partition key is composed from
todoId - The sort key is just the entity type prefix (no additional attributes)
createdAtandupdatedAtare automatically managed
4. Register on a Table
Section titled “4. Register on a Table”Register your entity on a table definition. The physical DynamoDB table name is not set here — it’s runtime configuration provided via MainTable.layer({ name: "..." }) when you run the program.
import { Table } from "effect-dynamodb"
const MainTable = Table.make({ schema: AppSchema, entities: { TodoEntity } })5. Use It
Section titled “5. Use It”Call DynamoClient.make({ entities, tables }) to get a typed client with executable operations. The typed client includes table management methods that derive the physical table definition (primary key, GSIs) from your registered entities — no manual CreateTable calls needed.
DynamoClient.make() resolves DynamoClient and TableConfig from the Effect context — you provide these as Layers when you run the program.
import { Effect, Console, Layer } from "effect"import { DynamoClient, Entity } from "effect-dynamodb"
const program = Effect.gen(function* () { // Required gateway — resolves dependencies, returns executable operations const db = yield* DynamoClient.make({ entities: { TodoEntity }, tables: { MainTable }, })
// Create the table — derived from entity definitions yield* db.tables.MainTable.create()
// Create const todo = yield* db.entities.TodoEntity.put({ todoId: "t-1", title: "Learn effect-dynamodb", done: false, }) yield* Console.log("Todo", todo) // { todoId: "t-1", title: "Learn effect-dynamodb", done: false, // createdAt: DateTime.Utc, updatedAt: DateTime.Utc }
// Read const found = yield* db.entities.TodoEntity.get({ todoId: "t-1" }) yield* Console.log("Found", found)
// Update yield* db.entities.TodoEntity.update({ todoId: "t-1" }).set({ done: true })
// Delete yield* db.entities.TodoEntity.delete({ todoId: "t-1" })})
// Run with DynamoDB Localconst main = program.pipe( Effect.provide( Layer.mergeAll( DynamoClient.layer({ region: "us-east-1", endpoint: "http://localhost:8000", credentials: { accessKeyId: "local", secretAccessKey: "local" }, }), // Physical DynamoDB table name — required runtime configuration MainTable.layer({ name: "quickstart" }), ) ))
Effect.runPromise(main)Save this as quickstart.ts and run it:
npx tsx quickstart.tsWhat Gets Stored in DynamoDB
Section titled “What Gets Stored in DynamoDB”For the put call above, effect-dynamodb generates:
| Attribute | Value |
|---|---|
pk | $myapp#v1#todo#t-1 |
sk | $myapp#v1#todo |
__edd_e__ | Todo |
todoId | t-1 |
title | Learn effect-dynamodb |
done | false |
createdAt | 2024-01-15T10:30:00.000Z |
updatedAt | 2024-01-15T10:30:00.000Z |
The key format ($myapp#v1#todo#t-1) is automatically generated from the schema, version, entity type, and composite attributes. You declare which attributes compose the key — the system handles how.
Optional Packages
Section titled “Optional Packages”Language Service Plugin
Section titled “Language Service Plugin”The TypeScript Language Service Plugin enhances IDE hover tooltips to show the DynamoDB operations that effect-dynamodb will execute. Hover over db.entities.Users.get(...), db.entities.Users.byRole(...), or any entity operation to see the underlying DynamoDB command, key structure, and index being used.
pnpm add -D @effect-dynamodb/language-serviceAdd the plugin to your tsconfig.json:
{ "compilerOptions": { "plugins": [ { "name": "@effect-dynamodb/language-service" } ] }}For example, hovering over get in the quick start above shows the underlying DynamoDB command (scroll to the bottom of the tooltip):
┌──────────────────────────────────────────────┐ │ ... │ │ ... │ │ │ │ effect-dynamodb — Native DynamoDB operation │ │ │ │ GetItemCommand({ │ │ TableName: "{TableName}", │ │ Key: { │ │ pk: "$myapp#v1#todo#t-1", │ │ sk: "$myapp#v1#todo", │ │ }, │ │ }) │ └──────────────────────────────────┬──────────-┘ ▼ const found = yield* db.entities.TodoEntity.get({ todoId: "t-1" })The plugin detects get, put, create, update, delete, query, and scan operations, resolves entities across imported modules, and shows composed keys and expressions — all directly in your editor’s hover tooltip.
Geospatial Package
Section titled “Geospatial Package”For proximity-based queries (e.g., “find all vehicles within 2 km”), the geo package adds H3 hexagonal grid indexing on top of effect-dynamodb entities.
pnpm add @effect-dynamodb/geoSee the Geospatial guide for setup and usage.
What’s Next?
Section titled “What’s Next?”- Modeling — Learn about Schema.Class models, DynamoSchema, Table, Entity, and derived types
- Indexes & Collections — Define access patterns with primary and secondary indexes
- Queries — Use the pipeable Query API for composable, type-safe queries
- Data Integrity — Unique constraints, versioning, and optimistic concurrency
- Lifecycle — Soft delete, restore, purge, and version retention
- Advanced — Rich updates, batch operations, multi-table design
- DynamoDB Streams — Decode stream records into typed domain objects
- Testing — Mock DynamoClient and test patterns