API with NestJS #21. An introduction to CQRS

JavaScript NestJS TypeScript

This entry is part 21 of 121 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.

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

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

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

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

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

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 invokes the method as soon as the is executed. It does so, thanks to the fact that we’ve used the decorator.

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

comments.module.ts

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

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

comments.controller.ts

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

Above, we’ve also created the that can transform the 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

As soon as we execute the , the calls the 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 frameworkAPI with NestJS #22. Storing JSON with PostgreSQL and TypeORM >>
Subscribe
Notify of
guest
6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Dmytro
Dmytro
4 years ago

Thank you a lot!

Noob
Noob
4 years ago

hey thanks

Den
Den
4 years ago

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!

Last edited 4 years ago by Den
Jamie Corkhill
Jamie Corkhill
4 years ago

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).

AFIF
AFIF
3 years ago
Reply to  Jamie Corkhill

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?

Lloyd
Lloyd
3 years ago

Need to cover Events too