CQRS
01 / 02

CQRS Fundamentals: Commands, Queries & Handlers

CQRS: Commands, Queries & Handlers

CQRS (Command Query Responsibility Segregation) separates the model/path for WRITING data (commands) from the model/path for READING data (queries) -- rather than one unified model serving both, as in traditional CRUD. It extends the older, method-level Command-Query Separation (CQS) principle up to full architectural scale.

Commands vs. Queries

// Command: intent to CHANGE state, typically returns little
// beyond an acknowledgment
interface PlaceOrderCommand {
  customerId: string
  items: OrderItem[]
}

// Query: request to READ data, never changes state
interface GetOrderHistoryQuery {
  customerId: string
}

Command Handlers

// Validates input, enforces business rules, applies the change --
// one focused handler per command type, not one sprawling function
class PlaceOrderCommandHandler {
  async handle(command: PlaceOrderCommand): Promise<{ orderId: string }> {
    validateInventory(command.items)
    const order = createOrder(command)
    await writeStore.save(order)
    // Full order details are NOT returned here -- that's a
    // separate query responsibility, keeping commands narrowly focused
    return { orderId: order.id }
  }
}

Query Handlers

// Reads and shapes data -- no business rule enforcement,
// often reads from a read-optimized, denormalized store
class GetOrderHistoryQueryHandler {
  async handle(query: GetOrderHistoryQuery): Promise<OrderSummary[]> {
    return readStore.getOrdersByCustomer(query.customerId)
  }
}

Why Separate Them

A high-read-volume system (a social feed) can optimize its read model purely for fast querying (denormalized, cache-friendly), decoupled from the write model that enforces business rules on writes -- a single shared model trying to serve both well tends to compromise on each.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free