Skip to content

Example: Batch Operations

A multi-entity example demonstrating Batch.get and Batch.write for efficient bulk operations. DynamoDB imposes limits on batch sizes (100 items for gets, 25 for writes) — effect-dynamodb handles chunking and unprocessed-item retries transparently.

What you’ll learn:

  • Batch.write() to create and delete items across entities in one call
  • Batch.get() to fetch items across entities with typed tuple returns
  • Auto-chunking beyond DynamoDB’s batch limits
  • Handling missing items (undefined in the result tuple)
  • Mixing puts and deletes in a single batch write

Two pure domain models — a User and an Order:

models.ts
const Role = { Admin: "admin", Member: "member" } as const
const RoleSchema = Schema.Literals(Object.values(Role))
const OrderStatus = { Pending: "pending", Shipped: "shipped", Delivered: "delivered" } as const
const OrderStatusSchema = Schema.Literals(Object.values(OrderStatus))
class User extends Schema.Class<User>("User")({
userId: Schema.String,
email: Schema.String,
name: Schema.NonEmptyString,
role: RoleSchema,
}) {}
class Order extends Schema.Class<Order>("Order")({
orderId: Schema.String,
userId: Schema.String,
product: Schema.NonEmptyString,
quantity: Schema.Number,
status: OrderStatusSchema,
}) {}

Both entities share a single table. Orders have a GSI index for querying by user:

entities.ts
const AppSchema = DynamoSchema.make({ name: "batch-demo", version: 1 })
const Users = Entity.make({
model: User,
entityType: "User",
primaryKey: {
pk: { field: "pk", composite: ["userId"] },
sk: { field: "sk", composite: [] },
},
timestamps: true,
})
const Orders = Entity.make({
model: Order,
entityType: "Order",
primaryKey: {
pk: { field: "pk", composite: ["orderId"] },
sk: { field: "sk", composite: [] },
},
indexes: {
byUser: {
name: "gsi1",
pk: { field: "gsi1pk", composite: ["userId"] },
sk: { field: "gsi1sk", composite: ["orderId"] },
},
},
timestamps: true,
})
const MainTable = Table.make({ schema: AppSchema, entities: { Users, Orders } })

Step 3: Batch Write — Create Multiple Items

Section titled “Step 3: Batch Write — Create Multiple Items”

Batch.write accepts a mix of put and delete operations across any number of entity types. DynamoDB limits batch writes to 25 items per request — effect-dynamodb auto-chunks larger batches transparently.

// Batch.write accepts a mix of put and delete operations.
// DynamoDB limits batch writes to 25 items per request —
// effect-dynamodb auto-chunks larger batches transparently.
yield* Batch.write([
Users.put({ userId: "u-1", email: "alice@example.com", name: "Alice", role: "admin" }),
Users.put({ userId: "u-2", email: "bob@example.com", name: "Bob", role: "member" }),
Users.put({ userId: "u-3", email: "charlie@example.com", name: "Charlie", role: "member" }),
Orders.put({
orderId: "o-1",
userId: "u-1",
product: "Widget",
quantity: 2,
status: "pending",
}),
Orders.put({
orderId: "o-2",
userId: "u-1",
product: "Gadget",
quantity: 1,
status: "shipped",
}),
Orders.put({
orderId: "o-3",
userId: "u-2",
product: "Doohickey",
quantity: 5,
status: "pending",
}),
])

All six items are written in one DynamoDB BatchWriteItem call (or auto-chunked if the batch exceeds 25 items).


Step 4: Batch Get — Fetch Multiple Items

Section titled “Step 4: Batch Get — Fetch Multiple Items”

Batch.get returns a typed tuple — each position in the result matches the entity type of the corresponding input. DynamoDB limits batch gets to 100 items per request; larger batches are auto-chunked with automatic retry of unprocessed keys.

// Batch.get returns a typed tuple: each position matches the entity type.
// DynamoDB limits batch gets to 100 items per request —
// effect-dynamodb auto-chunks and retries unprocessed keys.
const [alice, bob, order1, order2] = yield* Batch.get([
Users.get({ userId: "u-1" }),
Users.get({ userId: "u-2" }),
Orders.get({ orderId: "o-1" }),
Orders.get({ orderId: "o-2" }),
])

Each result is Model | undefined. If an item does not exist in DynamoDB, the corresponding position is undefined:

const [existing, missing] = yield* Batch.get([
Users.get({ userId: "u-1" }),
Users.get({ userId: "u-nonexistent" }),
])

Batch.write accepts both Entity.put() and Entity.delete() operations in the same array:

yield* Batch.write([
// Add a new order
Orders.put({
orderId: "o-4",
userId: "u-3",
product: "Thingamajig",
quantity: 1,
status: "pending",
}),
// Delete an existing order
Orders.delete({ orderId: "o-3" }),
])
// Verify: o-3 is gone, o-4 exists
const [deleted, created] = yield* Batch.get([
Orders.get({ orderId: "o-3" }),
Orders.get({ orderId: "o-4" }),
])

Puts and deletes can target different entity types in the same batch. This is useful for operations that need to modify users and orders together:

yield* Batch.write([
Users.put({ userId: "u-4", email: "diana@example.com", name: "Diana", role: "admin" }),
Orders.put({
orderId: "o-5",
userId: "u-4",
product: "Gizmo",
quantity: 3,
status: "pending",
}),
Users.delete({ userId: "u-3" }),
])

Note that batch writes are not transactional — DynamoDB applies each item independently. If you need atomic all-or-nothing semantics, use Transaction.transactWrite instead (limited to 100 items, 2x WCU cost).


The complete runnable example is at examples/batch.ts in the repository.

Terminal window
docker run -d -p 8000:8000 amazon/dynamodb-local
Terminal window
npx tsx examples/batch.ts
main.ts
const AppLayer = Layer.mergeAll(
DynamoClient.layer({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: { accessKeyId: "local", secretAccessKey: "local" },
}),
MainTable.layer({ name: "batch-demo-table" }),
)
const main = program.pipe(Effect.provide(AppLayer))
Effect.runPromise(main).then(
() => console.log("\nDone."),
(err) => console.error("\nFailed:", err),
)

ConceptHow it’s used
Batch.write()Create and delete items across entity types in one call
Batch.get()Fetch items across entity types with typed tuple returns
Auto-chunkingBatches exceeding DynamoDB limits (25 write, 100 get) are split transparently
Unprocessed retriesUnprocessed items/keys are retried automatically with exponential backoff
Typed tuplesEach position in the Batch.get result is typed to its input entity
Missing itemsNon-existent items return undefined in the result tuple
Mixed operationsPuts and deletes from different entities compose in one Batch.write call