Queries and Read-Model Projections
Separate reads with QueryBus handlers backed by optimized denormalized projections.
Why a Separate Read Side?
In CQRS (Command Query Responsibility Segregation) you split the system into a write side (commands that mutate state) and a read side (queries that return data). The two have fundamentally different needs.
- The write model is optimized for consistency and business invariants — normalized aggregates.
- The read model is optimized for fast, shape-perfect reads — denormalized projections tailored to each screen or endpoint.
A projection is a precomputed, query-friendly view of your data, usually built by listening to domain events. Instead of joining six tables at request time, the query handler reads one already-shaped row.
Queries Are Not Commands
A query is a plain DTO describing what the caller wants to read. It carries no behavior and must never mutate state. In @nestjs/cqrs, queries flow through the QueryBus to a matching @QueryHandler.
Keep queries free of domain rules. Their only job is to name an intent and carry parameters (ids, filters, paging). All the heavy lifting lives in the handler against the read model.
export class GetOrderSummaryQuery {
constructor(
public readonly orderId: string,
public readonly tenantId: string,
) {}
}
export class ListCustomerOrdersQuery {
constructor(
public readonly customerId: string,
public readonly page = 1,
public readonly pageSize = 20,
) {}
}All lessons in this course
- Commands, Handlers, and the Command Bus
- Queries and Read-Model Projections
- Domain Events and AggregateRoot
- Sagas for Long-Running Workflows