CQRS & Event-Driven Architecture
CQRS (Command Query Responsibility Segregation) is an architectural pattern that separates operations which change state (commands) from operations that read state (queries). Paired with an event bus, it enables loosely coupled, highly scalable enterprise systems. NestJS ships a first-class @nestjs/cqrs package that wires up the entire pattern with minimal boilerplate.
Why CQRS?
- Separation of concerns — write paths and read paths have different scaling, validation, and optimisation needs.
- Auditability — every state change is an explicit command; you can log, replay, or project events.
- Testability — handlers are plain classes; unit testing a command handler needs no HTTP layer.
- Event sourcing compatibility — CQRS is the natural companion to event sourcing and sagas.
Setting up @nestjs/cqrs
npm install @nestjs/cqrs
Import CqrsModule in the feature module. It registers CommandBus, QueryBus, and EventBus as providers that you can inject anywhere.
import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { OrdersController } from './orders.controller';
import { PlaceOrderHandler } from './commands/place-order.handler';
import { GetOrderHandler } from './queries/get-order.handler';
import { OrderPlacedHandler } from './events/order-placed.handler';
@Module({
imports: [CqrsModule],
controllers: [OrdersController],
providers: [PlaceOrderHandler, GetOrderHandler, OrderPlacedHandler],
})
export class OrdersModule {}
Commands and Command Handlers
A command is a plain class that carries the intent and data for a single state-changing action. A command handler is decorated with @CommandHandler and implements ICommandHandler:
// place-order.command.ts
export class PlaceOrderCommand {
constructor(
public readonly userId: string,
public readonly items: { productId: string; qty: number }[],
) {}
}
// place-order.handler.ts
import { CommandHandler, ICommandHandler, EventBus } from '@nestjs/cqrs';
import { PlaceOrderCommand } from './place-order.command';
import { OrderPlacedEvent } from '../events/order-placed.event';
@CommandHandler(PlaceOrderCommand)
export class PlaceOrderHandler implements ICommandHandler<PlaceOrderCommand> {
constructor(
private readonly orderRepo: OrderRepository,
private readonly eventBus: EventBus,
) {}
async execute(command: PlaceOrderCommand): Promise<string> {
const order = await this.orderRepo.create(command.userId, command.items);
this.eventBus.publish(new OrderPlacedEvent(order.id, command.userId));
return order.id;
}
}
One handler per command. Each command class maps to exactly one handler. This is enforced at runtime — registering two handlers for the same command throws an error.
Queries and Query Handlers
Queries follow the same pattern but must never mutate state. They implement IQueryHandler and can return DTOs directly from a read-optimised store:
// get-order.query.ts
export class GetOrderQuery {
constructor(public readonly orderId: string) {}
}
// get-order.handler.ts
import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { GetOrderQuery } from './get-order.query';
@QueryHandler(GetOrderQuery)
export class GetOrderHandler implements IQueryHandler<GetOrderQuery> {
constructor(private readonly readStore: OrderReadRepository) {}
async execute(query: GetOrderQuery) {
return this.readStore.findById(query.orderId);
}
}
Events and Event Handlers
When a command handler publishes an event via EventBus, any number of event handlers decorated with @EventsHandler will be invoked. This is the backbone of event-driven side-effects (sending emails, updating projections, triggering other bounded contexts):
// order-placed.event.ts
export class OrderPlacedEvent {
constructor(public readonly orderId: string, public readonly userId: string) {}
}
// order-placed.handler.ts
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { OrderPlacedEvent } from './order-placed.event';
@EventsHandler(OrderPlacedEvent)
export class OrderPlacedHandler implements IEventHandler<OrderPlacedEvent> {
constructor(private readonly notifier: NotificationService) {}
handle(event: OrderPlacedEvent) {
this.notifier.sendConfirmation(event.userId, event.orderId);
}
}
Sagas — Orchestrating Long-Running Processes
A saga is an RxJS-based reactive flow that listens for one or more events and emits new commands in response. Sagas are ideal for multi-step workflows (e.g., payment → fulfilment → shipping):
import { Injectable } from '@nestjs/common';
import { Saga, ICommand, ofType } from '@nestjs/cqrs';
import { Observable, map } from 'rxjs';
import { OrderPlacedEvent } from '../events/order-placed.event';
import { StartFulfillmentCommand } from '../commands/start-fulfillment.command';
@Injectable()
export class OrderSaga {
@Saga()
orderPlaced = (events$: Observable<any>): Observable<ICommand> =>
events$.pipe(
ofType(OrderPlacedEvent),
map((event) => new StartFulfillmentCommand(event.orderId)),
);
}
Keep sagas stateless. A saga should only transform events into commands; never store state inside a saga class. Long-running process state belongs in the database (a Process Manager table).
Dispatching from a Controller
The controller stays thin — it simply builds the command or query object and dispatches it:
import { Controller, Post, Get, Body, Param } from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { PlaceOrderCommand } from './commands/place-order.command';
import { GetOrderQuery } from './queries/get-order.query';
@Controller('orders')
export class OrdersController {
constructor(
private readonly commandBus: CommandBus,
private readonly queryBus: QueryBus,
) {}
@Post()
placeOrder(@Body() dto: PlaceOrderDto) {
return this.commandBus.execute(new PlaceOrderCommand(dto.userId, dto.items));
}
@Get(':id')
getOrder(@Param('id') id: string) {
return this.queryBus.execute(new GetOrderQuery(id));
}
}
Summary
@nestjs/cqrs brings Commands, Queries, Events, and Sagas under a single, consistent model. Commands mutate state through a single handler; queries read without side-effects; events propagate outcomes to many handlers; and sagas use RxJS to orchestrate complex multi-step workflows. Together they form a scalable, auditable, and testable event-driven architecture that is the foundation of enterprise NestJS applications.