10

API with NestJS #21. An introduction to CQRS

 3 years ago
source link: https://wanago.io/2020/12/07/api-nestjs-introduction-cqrs/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
API with NestJS #21. An introduction to CQRS

JavaScript NestJS TypeScript

December 7, 2020
This entry is part 21 of 21 in the API with NestJS

So far, in our application, we’ve been following a pattern of controllers using services to access and modify the data. While it is a very valid approach, there are other possibilities to look into.

NestJS suggests command-query responsibility segregation (CQRS). In this article, we look into this concept and implement it into our application.

Instead of keeping our logic in services, with CQRS, we use commands to update data and queries to read it. Therefore, we have a separation between performing actions and extracting data. While this might not be beneficial for simple CRUD applications, CQRS might make it easier to incorporate a complex business logic.

Doing the above forces us to avoid mixing domain logic and infrastructural operations. Therefore, it works well with Domain-Driven Design.

Domain-Driven Design is a very broad topic and it will be covered separately

Implementing CQRS with NestJS

The very first thing to do is to install a new package. It includes all of the utilities we need in this article.

npm install --save @nestjs/cqrs

Let’s explore CQRS by creating a new module in our application that we’ve been working on in this series. This time, we add a comments module.

comment.entity.ts
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import User from '../users/user.entity';
import Post from '../posts/post.entity';
@Entity()
class Comment {
  @PrimaryGeneratedColumn()
  public id: number;
  @Column()
  public content: string;
  @ManyToOne(() => Post, (post: Post) => post.comments)
  public post: Post;
  @ManyToOne(() => User, (author: User) => author.posts)
  public author: User;

If you want to know more on creating entities with relationships, check out API with NestJS #7. Creating relationships with Postgres and TypeORM

createComment.dto.ts
import { IsString, IsNotEmpty, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import ObjectWithIdDTO from 'src/utils/types/objectWithId.dto';
export class CreateCommentDto {
  @IsString()
  @IsNotEmpty()
  content: string;
  @ValidateNested()
  @Type(() => ObjectWithIdDTO)
  post: ObjectWithIdDTO;
export default CreateCommentDto;

We tackle the topic of validating DTO classes in API with NestJS #4. Error handling and data validation

Executing commands

With CQRS, we perform actions by executing commands. We first need to define them.

createComment.command.ts
import CreateCommentDto from '../../dto/createComment.dto';
import User from '../../../users/user.entity';
export class CreateCommentCommand {
  constructor(
    public readonly comment: CreateCommentDto,
    public readonly author: User,

To execute the above command, we need to use a command bus. Although the official documentation suggests that we can create services, we can execute commands straight in our controllers. In fact, this is what the creator of NestJS does during his talk at JS Kongress.

comments.controller.ts
import {
  Body,
  ClassSerializerInterceptor,
  Controller,
  Post,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard';
import RequestWithUser from '../authentication/requestWithUser.interface';
import CreateCommentDto from './dto/createComment.dto';
import { CommandBus } from '@nestjs/cqrs';
import { CreateCommentCommand } from './commands/implementations/createComment.command';
@Controller('comments')
@UseInterceptors(ClassSerializerInterceptor)
export default class CommentsController {
  constructor(private commandBus: CommandBus) {}
  @Post()
  @UseGuards(JwtAuthenticationGuard)
  async createComment(@Body() comment: CreateCommentDto, @Req() req: RequestWithUser) {
    const user = req.user;
    return this.commandBus.execute(
      new CreateCommentCommand(comment, user)

Above, we use the fact that the user that creates the comment is authenticated. We tackle this issue in API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies

Once we execute a certain command, it gets picked up by a matching command handler.

createComment.handler.ts
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { CreateCommentCommand } from '../implementations/createComment.command';
import { InjectRepository } from '@nestjs/typeorm';
import Comment from '../../comment.entity';
import { Repository } from 'typeorm';
@CommandHandler(CreateCommentCommand)
export class CreateCommentHandler implements ICommandHandler<CreateCommentCommand> {
  constructor(
    @InjectRepository(Comment)
    private commentsRepository: Repository<Comment>,
  async execute(command: CreateCommentCommand) {
    const newPost = await this.commentsRepository.create({
      ...command.comment,
      author: command.author
    await this.commentsRepository.save(newPost);
    return newPost;

In this handler we use a repository provided by TypeORM. If you want to explore this concept more, check out API with NestJS #2. Setting up a PostgreSQL database with TypeORM

The CreateCommentHandler invokes the execute method as soon as the CreateCommentCommand is executed. It does so, thanks to the fact that we’ve used the @CommandHandler(CreateCommentCommand) decorator.

We need to put all of the above in a module. Please notice that we also import the CqrsModule here.

comments.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import Comment from './comment.entity';
import CommentsController from './comments.controller';
import { CqrsModule } from '@nestjs/cqrs';
import { CreateCommentHandler } from './commands/handlers/create-comment.handler';
@Module({
  imports: [TypeOrmModule.forFeature([Comment]), CqrsModule],
  controllers: [CommentsController],
  providers: [CreateCommentHandler],
export class CommentsModule {}

Doing all of that gives us a fully functional controller that can add comments through executing commands. Once we execute the commands, the command handler reacts to it and performs the logic that creates a comment.

Querying data

Another important aspect of CQRS is querying data. The official documentation does not provide an example, but a Github repository can be used as such.

Let’s start by defining our query. Just as with commands, queries can also carry some additional data.

getComments.query.ts
export class GetCommentsQuery {
  constructor(
    public readonly postId?: number,

To execute a query, we need an instance of the QueryBus. It acts in a very similar way to the CommandBus.

comments.controller.ts
import {
  Body,
  ClassSerializerInterceptor,
  Controller, Get,
  Post, Query,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard';
import RequestWithUser from '../authentication/requestWithUser.interface';
import CreateCommentDto from './dto/createComment.dto';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { CreateCommentCommand } from './commands/implementations/createComment.command';
import { GetCommentsQuery } from './queries/implementations/getComments.query';
import GetCommentsDto from './dto/getComments.dto';
@Controller('comments')
@UseInterceptors(ClassSerializerInterceptor)
export default class CommentsController {
  constructor(
    private commandBus: CommandBus,
    private queryBus: QueryBus,
  @Post()
  @UseGuards(JwtAuthenticationGuard)
  async createComment(@Body() comment: CreateCommentDto, @Req() req: RequestWithUser) {
    const user = req.user;
    return this.commandBus.execute(
      new CreateCommentCommand(comment, user)
  @Get()
  async getComments(
    @Query() { postId }: GetCommentsDto,
    return this.queryBus.execute(
      new GetCommentsQuery(postId)

When we execute the query, the query handler picks it up.

Above, we’ve also created the GetCommentsDto that can transform the postId from a string to a number. If you want to know more about serialization, look into API with NestJS #5. Serializing the response with interceptors

getComments.handler.ts
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { GetCommentsQuery } from '../implementations/getComments.query';
import { InjectRepository } from '@nestjs/typeorm';
import Comment from '../../comment.entity';
import { Repository } from 'typeorm';
@QueryHandler(GetCommentsQuery)
export class GetCommentsHandler implements IQueryHandler<GetCommentsQuery> {
  constructor(
    @InjectRepository(Comment)
    private commentsRepository: Repository<Comment>,
  async execute(query: GetCommentsQuery) {
    if (query.postId) {
      return this.commentsRepository.find({
        post: {
          id: query.postId
    return this.commentsRepository.find();

As soon as we execute the GetCommentsQuery, the GetCommentsHandler calls the execute method to get our data.

Summary

This article introduced the concept of CQRS and implemented a straightforward example within our NestJS application. There are still more topics to cover when it comes to CQRS, such as events and sagas. Other patterns also work very well with CQRS, such as Event Sourcing. All of the above deserve separate articles, though.

Knowing the basics of CQRS, we know have yet another tool to consider when designing our architecture.

Series Navigation<< API with NestJS #20. Communicating with microservices using the gRPC framework

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK