Skip to content

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.

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:

Terminal window
docker run -d -p 8000:8000 --name dynamodb-local amazon/dynamodb-local

This starts DynamoDB Local on port 8000 in the background. The container persists until you stop it:

Terminal window
docker stop dynamodb-local # stop
docker start dynamodb-local # restart later
docker rm dynamodb-local # remove when done

To verify it’s running:

Terminal window
export AWS_ACCESS_KEY_ID=local
export AWS_SECRET_ACCESS_KEY=local
aws dynamodb list-tables --endpoint-url http://localhost:8000 --region us-east-1
Terminal window
pnpm add effect-dynamodb effect @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb

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,
}) {}
import { DynamoSchema } from "effect-dynamodb"
const AppSchema = DynamoSchema.make({
name: "myapp",
version: 1,
})

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)
  • createdAt and updatedAt are automatically managed

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 } })

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 Local
const 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:

Terminal window
npx tsx quickstart.ts

For the put call above, effect-dynamodb generates:

AttributeValue
pk$myapp#v1#todo#t-1
sk$myapp#v1#todo
__edd_e__Todo
todoIdt-1
titleLearn effect-dynamodb
donefalse
createdAt2024-01-15T10:30:00.000Z
updatedAt2024-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.

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.

Terminal window
pnpm add -D @effect-dynamodb/language-service

Add 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.

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.

Terminal window
pnpm add @effect-dynamodb/geo

See the Geospatial guide for setup and usage.

  • 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