ElectroDB: Collection vs Entity (When to Use Each)
An ElectroDB Entity represents one entity type. A Collection represents a group of entities that share a partition key, and lets you fetch all of them in one DynamoDB Query call.
The difference is simple. The question is when a collection is the right tool.
Quick definition
// Entity: query one entity type
const { data: orders } = await OrderEntity.query
.byCustomer({ customerId: "cust_01" })
.go()
// Collection: query multiple entity types from the same partition
const { data } = await AppService.collections
.customerWithOrders({ customerId: "cust_01" })
.go()
// data.customers → Customer[]
// data.orders → Order[]
// One DynamoDB Query, both entity types returned
When to use a Collection
Use a collection when:
- Two or more entity types share the same partition key (same PK template in their ElectroDB index definitions)
- You regularly need both entity types together: profile + activity, team + members, post + comments
- Minimizing round trips matters: dashboard loads, page renders, latency-sensitive paths
The canonical example: Customer and Order both use CUSTOMER#<customerId> as their partition key. The Customer metadata record (sk=#METADATA) and Order records (sk=ORDER#<orderId>) live in the same DynamoDB partition. One Query on that partition returns both. A collection makes that one-call pattern explicit and type-safe.
// Define entities that share a PK template
const CustomerEntity = new Entity({
indexes: {
primary: {
pk: { field: "pk", composite: ["customerId"], template: "CUSTOMER#${customerId}" },
sk: { field: "sk", composite: [], template: "#METADATA" },
},
},
// ...
}, { client, table })
const OrderEntity = new Entity({
indexes: {
byCustomer: {
// Same pk template as Customer → they share the partition
pk: { field: "pk", composite: ["customerId"], template: "CUSTOMER#${customerId}" },
sk: { field: "sk", composite: ["orderId"], template: "ORDER#${orderId}" },
},
},
// ...
}, { client, table })
// Group them in a Service
const AppService = new Service({ customers: CustomerEntity, orders: OrderEntity })
// Define the collection on the shared index
// (ElectroDB infers the collection automatically when PK templates match)
const { data } = await AppService.collections
.customerWithOrders({ customerId: "cust_01" })
.go()
When to use Entity queries separately
Use entity queries (not collections) when:
You only need one entity type. Fetching just orders for a dropdown doesn’t benefit from fetching customer metadata. Separate entity query, single entity type.
The entities don’t share a partition key. Collections only work for entities in the same partition. If Order uses ORDER#<orderId> as its primary PK and Customer uses CUSTOMER#<id>, they can’t share a collection on the main table (though a GSI that co-locates them would enable a collection on that GSI index).
You need separate pagination or filtering. Collections return all matching items across entity types. If you need “first 20 orders for a customer” without the customer metadata, an entity query with limit(20) is cleaner.
You’re querying a GSI. Collections can query GSIs too, but the entity types must both write to the same GSI with compatible key templates. This is less common; typically collections are used on the main table.
The underlying DynamoDB operation
A collection is always a single DynamoDB Query. ElectroDB sends one request, receives all items from that partition that match the key template, and then splits the results by entity type using each entity’s internal discriminator attribute (__edb_e__). You get back a typed object with one array per entity type.
No ElectroDB magic: it’s just one Query call, one response, sorted by entity type client-side.
Common mistake: expecting a collection where PK templates don’t match
// This will NOT work as a collection
const CustomerEntity = new Entity({
indexes: {
primary: {
pk: { field: "pk", composite: ["customerId"], template: "CUSTOMER#${customerId}" },
}
}
})
const OrderEntity = new Entity({
indexes: {
primary: {
pk: { field: "pk", composite: ["orderId"], template: "ORDER#${orderId}" }, // different pk!
},
byCustomer: {
pk: { field: "pk", composite: ["customerId"], template: "CUSTOMER#${customerId}" }, // same pk as Customer
}
}
})
Here, Order’s primary index uses ORDER#<orderId> but its byCustomer index uses CUSTOMER#<id>. A collection can use the byCustomer index, as long as the Service definition references the right index for each entity.
ElectroDB collections work on a specific named index. The entities must use the same PK template on that index. If they don’t share an index, they can’t form a collection.
Pattern reference
The E-Commerce Orders pattern uses Customer + Order with a shared partition: the collection pattern described here. The Many-to-Many Relationships pattern uses a collection to fetch Team + Membership records in one call.
All patterns on singletable.dev include ElectroDB entity definitions. The ElectroDB vs DynamoDB-Toolbox comparison covers why I use ElectroDB for single-table design specifically.