- 1. API with NestJS #1. Controllers, routing and the module structure
- 2. API with NestJS #2. Setting up a PostgreSQL database with TypeORM
- 3. API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies
- 4. API with NestJS #4. Error handling and data validation
- 5. API with NestJS #5. Serializing the response with interceptors
- 6. API with NestJS #6. Looking into dependency injection and modules
- 7. API with NestJS #7. Creating relationships with Postgres and TypeORM
- 8. API with NestJS #8. Writing unit tests
- 9. API with NestJS #9. Testing services and controllers with integration tests
- 10. API with NestJS #10. Uploading public files to Amazon S3
- 11. API with NestJS #11. Managing private files with Amazon S3
- 12. API with NestJS #12. Introduction to Elasticsearch
- 13. API with NestJS #13. Implementing refresh tokens using JWT
- 14. API with NestJS #14. Improving performance of our Postgres database with indexes
- 15. API with NestJS #15. Defining transactions with PostgreSQL and TypeORM
- 16. API with NestJS #16. Using the array data type with PostgreSQL and TypeORM
- 17. API with NestJS #17. Offset and keyset pagination with PostgreSQL and TypeORM
- 18. API with NestJS #18. Exploring the idea of microservices
- 19. API with NestJS #19. Using RabbitMQ to communicate with microservices
- 20. API with NestJS #20. Communicating with microservices using the gRPC framework
- 21. API with NestJS #21. An introduction to CQRS
- 22. API with NestJS #22. Storing JSON with PostgreSQL and TypeORM
- 23. API with NestJS #23. Implementing in-memory cache to increase the performance
- 24. API with NestJS #24. Cache with Redis. Running the app in a Node.js cluster
- 25. API with NestJS #25. Sending scheduled emails with cron and Nodemailer
- 26. API with NestJS #26. Real-time chat with WebSockets
- 27. API with NestJS #27. Introduction to GraphQL. Queries, mutations, and authentication
- 28. API with NestJS #28. Dealing in the N + 1 problem in GraphQL
- 29. API with NestJS #29. Real-time updates with GraphQL subscriptions
- 30. API with NestJS #30. Scalar types in GraphQL
- 31. API with NestJS #31. Two-factor authentication
- 32. API with NestJS #32. Introduction to Prisma with PostgreSQL
- 33. API with NestJS #33. Managing PostgreSQL relationships with Prisma
- 34. API with NestJS #34. Handling CPU-intensive tasks with queues
- 35. API with NestJS #35. Using server-side sessions instead of JSON Web Tokens
- 36. API with NestJS #36. Introduction to Stripe with React
- 37. API with NestJS #37. Using Stripe to save credit cards for future use
- 38. API with NestJS #38. Setting up recurring payments via subscriptions with Stripe
- 39. API with NestJS #39. Reacting to Stripe events with webhooks
- 40. API with NestJS #40. Confirming the email address
- 41. API with NestJS #41. Verifying phone numbers and sending SMS messages with Twilio
- 42. API with NestJS #42. Authenticating users with Google
- 43. API with NestJS #43. Introduction to MongoDB
- 44. API with NestJS #44. Implementing relationships with MongoDB
- 45. API with NestJS #45. Virtual properties with MongoDB and Mongoose
- 46. API with NestJS #46. Managing transactions with MongoDB and Mongoose
- 47. API with NestJS #47. Implementing pagination with MongoDB and Mongoose
- 48. API with NestJS #48. Definining indexes with MongoDB and Mongoose
- 49. API with NestJS #49. Updating with PUT and PATCH with MongoDB and Mongoose
- 50. API with NestJS #50. Introduction to logging with the built-in logger and TypeORM
- 51. API with NestJS #51. Health checks with Terminus and Datadog
- 52. API with NestJS #52. Generating documentation with Compodoc and JSDoc
- 53. API with NestJS #53. Implementing soft deletes with PostgreSQL and TypeORM
- 54. API with NestJS #54. Storing files inside a PostgreSQL database
- 55. API with NestJS #55. Uploading files to the server
- 56. API with NestJS #56. Authorization with roles and claims
- 57. API with NestJS #57. Composing classes with the mixin pattern
- 58. API with NestJS #58. Using ETag to implement cache and save bandwidth
- 59. API with NestJS #59. Introduction to a monorepo with Lerna and Yarn workspaces
- 60. API with NestJS #60. The OpenAPI specification and Swagger
- 61. API with NestJS #61. Dealing with circular dependencies
- 62. API with NestJS #62. Introduction to MikroORM with PostgreSQL
- 63. API with NestJS #63. Relationships with PostgreSQL and MikroORM
- 64. API with NestJS #64. Transactions with PostgreSQL and MikroORM
- 65. API with NestJS #65. Implementing soft deletes using MikroORM and filters
- 66. API with NestJS #66. Improving PostgreSQL performance with indexes using MikroORM
- 67. API with NestJS #67. Migrating to TypeORM 0.3
- 68. API with NestJS #68. Interacting with the application through REPL
- 69. API with NestJS #69. Database migrations with TypeORM
- 70. API with NestJS #70. Defining dynamic modules
- 71. API with NestJS #71. Introduction to feature flags
- 72. API with NestJS #72. Working with PostgreSQL using raw SQL queries
- 73. API with NestJS #73. One-to-one relationships with raw SQL queries
- 74. API with NestJS #74. Designing many-to-one relationships using raw SQL queries
- 75. API with NestJS #75. Many-to-many relationships using raw SQL queries
- 76. API with NestJS #76. Working with transactions using raw SQL queries
- 77. API with NestJS #77. Offset and keyset pagination with raw SQL queries
- 78. API with NestJS #78. Generating statistics using aggregate functions in raw SQL
- 79. API with NestJS #79. Implementing searching with pattern matching and raw SQL
- 80. API with NestJS #80. Updating entities with PUT and PATCH using raw SQL queries
- 81. API with NestJS #81. Soft deletes with raw SQL queries
- 82. API with NestJS #82. Introduction to indexes with raw SQL queries
- 83. API with NestJS #83. Text search with tsvector and raw SQL
- 84. API with NestJS #84. Implementing filtering using subqueries with raw SQL
- 85. API with NestJS #85. Defining constraints with raw SQL
- 86. API with NestJS #86. Logging with the built-in logger when using raw SQL
- 87. API with NestJS #87. Writing unit tests in a project with raw SQL
- 88. API with NestJS #88. Testing a project with raw SQL using integration tests
- 89. API with NestJS #89. Replacing Express with Fastify
- 90. API with NestJS #90. Using various types of SQL joins
- 91. API with NestJS #91. Dockerizing a NestJS API with Docker Compose
- 92. API with NestJS #92. Increasing the developer experience with Docker Compose
- 93. API with NestJS #93. Deploying a NestJS app with Amazon ECS and RDS
- 94. API with NestJS #94. Deploying multiple instances on AWS with a load balancer
- 95. API with NestJS #95. CI/CD with Amazon ECS and GitHub Actions
- 96. API with NestJS #96. Running unit tests with CI/CD and GitHub Actions
- 97. API with NestJS #97. Introduction to managing logs with Amazon CloudWatch
- 98. API with NestJS #98. Health checks with Terminus and Amazon ECS
- 99. API with NestJS #99. Scaling the number of application instances with Amazon ECS
- 100. API with NestJS #100. The HTTPS protocol with Route 53 and AWS Certificate Manager
- 101. API with NestJS #101. Managing sensitive data using the AWS Secrets Manager
- 102. API with NestJS #102. Writing unit tests with Prisma
- 103. API with NestJS #103. Integration tests with Prisma
- 104. API with NestJS #104. Writing transactions with Prisma
- 105. API with NestJS #105. Implementing soft deletes with Prisma and middleware
- 106. API with NestJS #106. Improving performance through indexes with Prisma
- 107. API with NestJS #107. Offset and keyset pagination with Prisma
- 108. API with NestJS #108. Date and time with Prisma and PostgreSQL
- 109. API with NestJS #109. Arrays with PostgreSQL and Prisma
- 110. API with NestJS #110. Managing JSON data with PostgreSQL and Prisma
- 111. API with NestJS #111. Constraints with PostgreSQL and Prisma
- 112. API with NestJS #112. Serializing the response with Prisma
- 113. API with NestJS #113. Logging with Prisma
- 114. API with NestJS #114. Modifying data using PUT and PATCH methods with Prisma
- 115. API with NestJS #115. Database migrations with Prisma
- 116. API with NestJS #116. REST API versioning
- 117. API with NestJS #117. CORS – Cross-Origin Resource Sharing
- 118. API with NestJS #118. Uploading and streaming videos
- 119. API with NestJS #119. Type-safe SQL queries with Kysely and PostgreSQL
- 120. API with NestJS #120. One-to-one relationships with the Kysely query builder
- 121. API with NestJS #121. Many-to-one relationships with PostgreSQL and Kysely
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.
1 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
1 2 3 4 5 6 7 8 9 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import { Body, ClassSerializerInterceptor, Controller, Post, Req, 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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
1 2 3 4 5 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import { Body, ClassSerializerInterceptor, Controller, Get, Post, Query, Req, 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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.
Thank you a lot!
hey thanks
Are you going to do basic frontend for your app in future? Using Angular for example. Or you’re focused on backend API only?
Of course, thanks a lot. Awesome guides!
I have to disagree with some of the concepts mentioned in this article – passing DTOs straight through to commands and using the repository in your queries.
For the first one, DTOs (in this context) are, fundamentally, a presentation layer concern. They describe the shape of data as it comes off-the-wire (the POST Request Body in this case). Commands are more of an application layer concern. In order to allow the presentation layer and the application layer to evolve separately, you should explicitly map from the DTO to the Command. Suppose, tomorrow, you need to dispatch two commands over the bus in that controller action. You still have only one DTO, which you’d need to modify to contain the sum of all the properties required for each command, and then you’d need to map that DTO between each Command. So, the point is, (trivial-projects and side-projects aside), the interface of the DTO and the interface of the Command should be separate/segregated (even if they start out the same since they might eventually diverge), and there should be an explicit mapping between them.
For the second point, you’re having your queries reach out directly to the Repository, and that Repository is the same Repository you use on your write-side. So, because of that, you’ve got all this extra CQRS-complexity for literally no gain. It’s very common for CQRS Queries to go directly to SQL to construct a read model specifically for the needs of the client in that particular context. What you’re doing here is absolutely no different than just calling the Repository from the Controller Action for GET Requests, except here, you’ve put a bus/mediator in between them. You’re using the same model for both sides – that’s not true CQRS.
In general, with DDD, Repositories are supposed to return Domain Models. Indeed, Domain Models are not at all designed with the read-requirements of the application in mind. They’re not optimized for reads and they will very seldom contain the data required for reads. So, the whole point of CQRS (literally the whole point of CQRS) is to segregate those models – to use a different model for writes (creating/updating) than you do for reads. But, here, you’re using the exact same model for both. I don’t really believe you can call that CQRS despite the fact that it seems like CQRS on the surface. In this case, you’re not doing DDD, which is totally fine, so the models are just one-to-one table mappings (that is, anemic – again, fine, since you’re not doing DDD), but table models aren’t optimized for reads. Your CQRS Queries should have all the power in the world to go directly to SQL and carve/massage data out the database exactly as the client needs. They should create a DTO/View Model for the client as per the data it requires, not just return the same model used for writes, because by doing that, you’re aren’t doing CQRS.
Finally, I didn’t see any mention of the fact that CQRS doesn’t stop here – it can be extended to having completely different data stores for reads and writes (meaning you accept Eventual Consistency).
I don’t mean to be overly-critical and it’s great you’re talking about these patterns in the NodeJS world. I just don’t want beginners to get the wrong idea because I fundamentally disagree with some of the practices you show here, especially number 2).
And for cases that commands return domain models?
1 – should the view layer do command then a query to get the data?
2 – could the query api use another repository that returns the DTO directly?
Need to cover Events too