What is CQRS pattern and how to implement it in NestJS?
CQRS in NestJS
CQRS (Command Query Responsibility Segregation) separates read operations (queries) from write operations (commands). NestJS provides the @nestjs/cqrs package for implementing this pattern.
Why CQRS?
| Without CQRS | With CQRS |
|---|---|
| One model for read & write | Separate models |
| Single database | Can use different DBs |
| Service does everything | Clear separation of concerns |
| Hard to scale reads/writes independently | Scale independently |
Setup
bash
npm install @nestjs/cqrstypescript
import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
@Module({
imports: [CqrsModule],
// ...
})
export class OrdersModule {}Commands (Write Operations)
Define Command
typescript
export class CreateOrderCommand {
constructor(
public readonly userId: string,
public readonly items: { productId: string; quantity: number }[],
public readonly shippingAddress: string,
) {}
}Command Handler
typescript
import { CommandHandler, ICommandHandler, EventBus } from '@nestjs/cqrs';
@CommandHandler(CreateOrderCommand)
export class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {
constructor(
private readonly orderRepository: OrderRepository,
private readonly eventBus: EventBus,
) {}
async execute(command: CreateOrderCommand) {
const { userId, items, shippingAddress } = command;
const order = await this.orderRepository.create({
userId,
items,
shippingAddress,
status: 'pending',
});
// Publish domain event
this.eventBus.publish(new OrderCreatedEvent(order.id, userId));
return order;
}
}Queries (Read Operations)
Define Query
typescript
export class GetOrderByIdQuery {
constructor(public readonly orderId: string) {}
}
export class GetUserOrdersQuery {
constructor(
public readonly userId: string,
public readonly page: number = 1,
) {}
}Query Handler
typescript
import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
@QueryHandler(GetOrderByIdQuery)
export class GetOrderByIdHandler implements IQueryHandler<GetOrderByIdQuery> {
constructor(private readonly orderReadRepository: OrderReadRepository) {}
async execute(query: GetOrderByIdQuery) {
return this.orderReadRepository.findById(query.orderId);
}
}Events
Define Event
typescript
export class OrderCreatedEvent {
constructor(
public readonly orderId: string,
public readonly userId: string,
) {}
}Event Handler
typescript
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
@EventsHandler(OrderCreatedEvent)
export class OrderCreatedHandler implements IEventHandler<OrderCreatedEvent> {
constructor(
private readonly emailService: EmailService,
private readonly analyticsService: AnalyticsService,
) {}
async handle(event: OrderCreatedEvent) {
await this.emailService.sendOrderConfirmation(event.userId, event.orderId);
await this.analyticsService.trackOrder(event.orderId);
}
}Using in Controller
typescript
import { CommandBus, QueryBus } from '@nestjs/cqrs';
@Controller('orders')
export class OrdersController {
constructor(
private readonly commandBus: CommandBus,
private readonly queryBus: QueryBus,
) {}
@Post()
async create(@Body() dto: CreateOrderDto, @CurrentUser() user: User) {
return this.commandBus.execute(
new CreateOrderCommand(user.id, dto.items, dto.shippingAddress),
);
}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.queryBus.execute(new GetOrderByIdQuery(id));
}
}Register Handlers in Module
typescript
const CommandHandlers = [CreateOrderHandler, CancelOrderHandler];
const QueryHandlers = [GetOrderByIdHandler, GetUserOrdersHandler];
const EventHandlers = [OrderCreatedHandler, OrderCancelledHandler];
@Module({
imports: [CqrsModule],
controllers: [OrdersController],
providers: [
...CommandHandlers,
...QueryHandlers,
...EventHandlers,
OrderRepository,
OrderReadRepository,
],
})
export class OrdersModule {}When to Use CQRS
| Use CQRS When | Don't Use When |
|---|---|
| Complex business logic | Simple CRUD apps |
| Different read/write models | Small projects |
| Event-driven architecture | Prototype/MVP |
| Need audit trail | Team unfamiliar with pattern |
| Independent scaling of reads/writes | Over-engineering risk |
Tip: CQRS adds complexity. Start simple with services. Introduce CQRS only when your read and write patterns diverge significantly, or when you need event sourcing.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.