As your applications grow in complexity, so does the challenge of organizing business logic in a maintainable and scalable way. One pattern that addresses this challenge is CQRS — Command Query Responsibility Segregation.
In this article, you'll learn what CQRS is and how it works, when to use (and avoid) CQRS, and how to implement CQRS in a real-world NestJS application using the @nestjs/cqrs
module
Enjoy
🤔 What is CQRS?
CQRS stands for Command Query Responsibility Segregation. It’s an architectural pattern that separates the responsibilities of:
- Commands: actions that change state (e.g., create, update, delete)
- Queries: actions that read data (e.g., get list of tasks, fetch user info)
This separation contrasts with traditional CRUD, where reads and writes are typically handled by the same service or controller.
It comes with following benefits:
- Separation of concerns: Easier to reason about logic when read and write flows are decoupled.
- Scalability: Queries and commands can be scaled independently.
- Flexibility: Enables different models for read/write operations (e.g., denormalized reads).
- Auditability: Commands can be logged or handled asynchronously with events.
Use CQRS when:
- Your app has complex business logic.
- Read and write operations have different scaling needs.
- You want to use event sourcing or asynchronous workflows.
- You're working in a microservices architecture.
✅ Implementing CQRS in Nest.js
NestJS promotes modularity and clean architecture by default. Its support for decorators, DI (Dependency Injection), and event-driven design makes it an excellent fit for CQRS.
The @nestjs/cqrs
package provides:
-
Command
andCommandHandler
-
Query
andQueryHandler
-
Event
andEventHandler
- Support for sagas, event buses, and command buses
We will showcase how CQRS works in Nest with an example. Let’s build a simple task manager with the following features:
- Create a task
- Fetch all tasks
1.Install the required package:
npm install @nestjs/cqrs
2.Create Domain Model
export interface Task {
id: string;
title: string;
description: string;
status: 'open' | 'in-progress' | 'done';
}
3.Create Command: CreateTaskCommand
export class CreateTaskCommand {
constructor(
public readonly title: string,
public readonly description: string
) {}
}
4.Create Command Handler: CreateTaskHandler
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
@CommandHandler(CreateTaskCommand)
export class CreateTaskHandler implements ICommandHandler<CreateTaskCommand> {
constructor(private readonly taskRepository: TaskRepository) {}
async execute(command: CreateTaskCommand) {
const { title, description } = command;
const task = {
id: crypto.randomUUID(),
title,
description,
status: 'open',
};
await this.taskRepository.create(task);
return task;
}
}
5.Create Query: GetAllTasksQuery
export class GetAllTasksQuery {
// query params
}
6.Create Query Handler: GetAllTasksHandler
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
@QueryHandler(GetAllTasksQuery)
export class GetAllTasksHandler implements IQueryHandler<GetAllTasksQuery> {
constructor(private readonly taskRepository: TaskRepository) {}
async execute(query: GetAllTasksQuery) {
return this.taskRepository.findAll();
}
}
7.Create Task Repository (In-Memory Example)
@Injectable()
export class TaskRepository {
private tasks: Task[] = [];
async create(task: Task) {
this.tasks.push(task);
return task;
}
async findAll(): Promise<Task[]> {
return this.tasks;
}
}
8.Register in Module
@Module({
imports: [CqrsModule],
providers: [
TaskRepository,
CreateTaskHandler,
GetAllTasksHandler,
],
})
export class TasksModule {}
9.Use in Controller
@Controller('tasks')
export class TasksController {
constructor(private readonly commandBus: CommandBus, private readonly queryBus: QueryBus) {}
@Post()
async createTask(@Body() dto: { title: string; description: string }) {
return this.commandBus.execute(new CreateTaskCommand(dto.title, dto.description));
}
@Get()
async getAllTasks() {
return this.queryBus.execute(new GetAllTasksQuery());
}
}
Summary
CQRS can dramatically improve the structure, maintainability, and scalability of your NestJS applications — when used appropriately. It shines in complex domains and larger systems, especially when used alongside patterns like event sourcing or microservices.
Take care and happy coding!
Top comments (0)