DEV Community

Cover image for CQRS Pattern in Nest.js
Jakub Andrzejewski
Jakub Andrzejewski

Posted on

CQRS Pattern in Nest.js

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 CQRSCommand 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 and CommandHandler
  • Query and QueryHandler
  • Event and EventHandler
  • 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
Enter fullscreen mode Exit fullscreen mode

2.Create Domain Model

export interface Task {
  id: string;
  title: string;
  description: string;
  status: 'open' | 'in-progress' | 'done';
}
Enter fullscreen mode Exit fullscreen mode

3.Create Command: CreateTaskCommand

export class CreateTaskCommand {
  constructor(
    public readonly title: string,
    public readonly description: string
  ) {}
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

5.Create Query: GetAllTasksQuery

export class GetAllTasksQuery {
  // query params
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

8.Register in Module

@Module({
  imports: [CqrsModule],
  providers: [
    TaskRepository,
    CreateTaskHandler,
    GetAllTasksHandler,
  ],
})
export class TasksModule {}
Enter fullscreen mode Exit fullscreen mode

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());
  }
}
Enter fullscreen mode Exit fullscreen mode

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)