NashTech Blog

Table of Contents

In simple CRUD(Create, Read, Update & Delete) applications the controller handles the HTTP requests and delegates the business logic to the services.

While this pattern is usually sufficient for small and medium-sized applications, it may not be the best choice for larger, more complex applications. In such cases, the CQRS (Command and Query Responsibility Segregation) model may be more appropriate and scalable.

In this blog post, we’ll delve into the world of CQRS and explore how it can be implemented in NestJS for crafting robust and efficient systems.

What is CQRS?

CQRS stands for Command-Query Responsibility Segregation. In simpler terms, it separates how you “read” and “write” data. Commands handle actions that modify the application state (think “Create”, “Update”, “Delete”), while queries focus solely on retrieving data without causing any change. This separation of reading and writing operations allows for more flexibility in designing and scaling systems.

Benefits of CQRS

  • Improved scalability: Read and write operations can be scaled independently, which is helpful for applications with high read/write volume disparities.
  • Simplified code: Separating concerns makes code easier to understand and maintain.
  • Enhanced performance: Optimized models for each type of operation can lead to faster response times.
  • Increased flexibility: Different technologies can be used for reads and writes, providing more options for specific needs.

Key concepts of CQRS

The key components of CQRS are as follows:

  • Commands: These are operations that change the state of the system. They represent actions such as creating, updating, or deleting data.
  • Queries: These are operations that retrieve data from the system without modifying its state. Queries do not have side effects and are used to read information.
  • Command Handlers: Components responsible for processing commands and updating the system’s state. They encapsulate the logic for handling the various command types.
  • Query Handlers: Components responsible for processing queries and retrieving data from the system. They encapsulate the logic for reading information.
  • Separate Models: CQRS often involves using separate data models for reading and writing. This means that the way data is stored and retrieved may differ from how it is presented or processed.
  • Event Sourcing: In some CQRS implementations, events are used to represent state changes. Instead of storing the current state, the system stores a sequence of events that can be replayed to reconstruct the state at any point in time.

Now that we understand about CQRS design pattern and its core components lets see how to implement the CQRS in NestJS.

Implementing CQRS in NestJS

Here, We’ll create a basic “todo” application with commands for adding and completing tasks and queries for retrieving tasks. Before starting implementation, make sure you have NestJS installed:

npm install -g @nestjs/cli

Now, let’s create a new NestJS project:

nest new nest-cqrs-todo-app
cd nest-cqrs-todo-app

Now, let’s create a module for our CQRS application:

nest generate module tasks

Inside the tasks module, let’s create the commands, queries, and handlers:

1. Create a file tasks.commands.ts:

import { ICommand } from '@nestjs/cqrs';

export class AddTaskCommand implements ICommand {
  constructor(public readonly title: string) {}
}

export class CompleteTaskCommand implements ICommand {
  constructor(public readonly taskId: string) {}
}

2. Create a file tasks.queries.ts:

import { IQuery } from '@nestjs/cqrs';

export class GetTasksQuery implements IQuery {}

3. Create a file tasks.handlers.ts:

import { CommandHandler, QueryHandler } from '@nestjs/cqrs';
import { AddTaskCommand, CompleteTaskCommand } from './tasks.commands';
import { GetTasksQuery } from './tasks.queries';

interface Task {
  id: string;
  title: string;
  completed: boolean;
}

const tasks: Task[] = [];

@CommandHandler(AddTaskCommand)
export class AddTaskHandler {
  execute(command: AddTaskCommand) {
    const task: Task = {
      id: tasks.length.toString(),
      title: command.title,
      completed: false,
    };
    tasks.push(task);
    return task;
  }
}

@CommandHandler(CompleteTaskCommand)
export class CompleteTaskHandler {
  execute(command: CompleteTaskCommand) {
    const task = tasks.find((t) => t.id === command.taskId);
    if (task) {
      task.completed = true;
      return task;
    }
    return null;
  }
}

@QueryHandler(GetTasksQuery)
export class GetTasksHandler {
  execute(query: GetTasksQuery) {
    return tasks;
  }
}

4. Modify the tasks.module.ts to include the commands, queries, and handlers:

import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import {
  AddTaskHandler,
  CompleteTaskHandler,
  GetTasksHandler,
} from './tasks.handlers';
import { TasksController } from './tasks.controller';

@Module({
  imports: [CqrsModule],
  controllers: [TasksController],
  providers: [AddTaskHandler, CompleteTaskHandler, GetTasksHandler],
})
export class TasksModule {}

5. Create a controller tasks.controller.ts:

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { AddTaskCommand, CompleteTaskCommand } from './tasks.commands';
import { GetTasksQuery } from './tasks.queries';

@Controller('tasks')
export class TasksController {
  constructor(
    private readonly commandBus: CommandBus,
    private readonly queryBus: QueryBus,
  ) {}

  @Post()
  async addTask(@Body('title') title: string) {
    return this.commandBus.execute(new AddTaskCommand(title));
  }

  @Post(':id/complete')
  async completeTask(@Body('id') id: string) {
    return this.commandBus.execute(new CompleteTaskCommand(id));
  }

  @Get()
  async getTasks() {
    return this.queryBus.execute(new GetTasksQuery());
  }
}

6. Lastly, update the app.module.ts to include the TasksModule:

import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { TasksModule } from './tasks/tasks.module';

@Module({
  imports: [CqrsModule, TasksModule],
  controllers: [],
  providers: [],
})
export class AppModule {}

Testing TODO Application

Now to test TODO application we can run the applicatio using command:

npm run start:dev

And test the endpoints using tools like curl, Postman, or your preferred API testing tool. Here are we can see the results of the endpoints:

Conclusion

The CQRS pattern provides a robust approach to separating concerns in applications by dividing read and write operations. Simplifying the implementation of the CQRS pattern in a NestJS application is made possible with the assistance of the @nestjs/cqrs package. Through the utilization of commands and queries, intricate data manipulation and querying scenarios can be managed efficiently, all while ensuring scalability and maintainability.

For a practical illustration of the concepts, you can refer to the following GitHub repository: REPO

For more such posts, please follow our LinkedIn page- FrontEnd Competency.

Picture of Deeksha

Deeksha

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top