CQRS
The flow of simple CRUD (Create, Read, Update and Delete) applications can be described as follows:
- The controllers layer handles HTTP requests and delegates tasks to the services layer.
- The services layer is where most of the business logic lives.
- Services use repositories / DAOs to change / persist entities.
- Entities act as containers for the values, with setters and getters.
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 (depending on the application's requirements). Benefits of this model include:
- Separation of concerns. The model separates the read and write operations into separate models.
- Scalability. The read and write operations can be scaled independently.
- Flexibility. The model allows for the use of different data stores for read and write operations.
- Performance. The model allows for the use of different data stores optimized for read and write operations.
To facilitate that model, Nest provides a lightweight CQRS module. This chapter describes how to use it.
Installation
First install the required package:
Once the installation is complete, navigate to the root module of your application (usually AppModule), and import the CqrsModule.forRoot():
This module accepts an optional configuration object. The following options are available:
Commands
Commands are used to change the application state. They should be task-based, rather than data centric. When a command is dispatched, it is handled by a corresponding Command Handler. The handler is responsible for updating the application state.
In the code snippet above, we instantiate the KillDragonCommand class and pass it to the CommandBus's execute() method. This is the demonstrated command class:
As you can see, the KillDragonCommand class extends the Command class. The Command class is a simple utility class exported from the @nestjs/cqrs package that lets you define the command's return type. In this case, the return type is an object with an actionId property. Now, whenever the KillDragonCommand command is dispatched, the CommandBus#execute() method return-type will be inferred as Promise<{ actionId: string }>. This is useful when you want to return some data from the command handler.
info Hint Inheritance from the
Commandclass is optional. It is only necessary if you want to define the return type of the command.
The CommandBus represents a stream of commands. It is responsible for dispatching commands to the appropriate handlers. The execute() method returns a promise, which resolves to the value returned by the handler.
Let's create a handler for the KillDragonCommand command.
This handler retrieves the Hero entity from the repository, calls the killEnemy() method, and then persists the changes. The KillDragonHandler class implements the ICommandHandler interface, which requires the implementation of the execute() method. The execute() method receives the command object as an argument.
Note that ICommandHandler<KillDragonCommand> forces you to return a value that matches the command's return type. In this case, the return type is an object with an actionId property. This only applies to commands that inherit from the Command class. Otherwise, you can return whatever you want.
Lastly, make sure to register the KillDragonHandler as a provider in a module:
Queries
Queries are used to retrieve data from the application state. They should be data centric, rather than task-based. When a query is dispatched, it is handled by a corresponding Query Handler. The handler is responsible for retrieving the data.
The QueryBus follows the same pattern as the CommandBus. Query handlers should implement the IQueryHandler interface and be annotated with the @QueryHandler() decorator. See the following example:
Similar to the Command class, the Query class is a simple utility class exported from the @nestjs/cqrs package that lets you define the query's return type. In this case, the return type is a Hero object. Now, whenever the GetHeroQuery query is dispatched, the QueryBus#execute() method return-type will be inferred as Promise<Hero>.
To retrieve the hero, we need to create a query handler:
The GetHeroHandler class implements the IQueryHandler interface, which requires the implementation of the execute() method. The execute() method receives the query object as an argument, and must return the data that matches the query's return type (in this case, a Hero object).
Lastly, make sure to register the GetHeroHandler as a provider in a module:
Now, to dispatch the query, use the QueryBus:
Events
Events are used to notify other parts of the application about changes in the application state. They are dispatched by models or directly using the EventBus. When an event is dispatched, it is handled by corresponding Event Handlers. Handlers can then, for example, update the read model.
For demonstration purposes, let's create an event class:
Now while events can be dispatched directly using the EventBus.publish() method, we can also dispatch them from the model. Let's update the Hero model to dispatch the HeroKilledDragonEvent event when the killEnemy() method is called.
The apply() method is used to dispatch events. It accepts an event object as an argument. However, since our model is not aware of the EventBus, we need to associate it with the model. We can do that by using the EventPublisher class.
The EventPublisher#mergeObjectContext method merges the event publisher into the provided object, which means that the object will now be able to publish events to the events stream.
Notice that in this example we also call the commit() method on the model. This method is used to dispatch any outstanding events. To automatically dispatch events, we can set the autoCommit property to true:
In case we want to merge the event publisher into a non-existing object, but rather into a class, we can use the EventPublisher#mergeClassContext method:
Now every instance of the HeroModel class will be able to publish events without using mergeObjectContext() method.
Additionally, we can emit events manually using EventBus:
info Hint The
EventBusis an injectable class.
Each event can have multiple Event Handlers.
info Hint Be aware that when you start using event handlers you get out of the traditional HTTP web context.
- Errors in
CommandHandlerscan still be caught by built-in Exception filters.- Errors in
EventHandlerscan't be caught by Exception filters: you will have to handle them manually. Either by a simpletry/catch, using Sagas by triggering a compensating event, or whatever other solution you choose.- HTTP Responses in
CommandHandlerscan still be sent back to the client.- HTTP Responses in
EventHandlerscannot. If you want to send information to the client you could use WebSocket, SSE, or whatever other solution you choose.
As with commands and queries, make sure to register the HeroKilledDragonHandler as a provider in a module:
Sagas
Saga is a long-running process that listens to events and may trigger new commands. It is usually used to manage complex workflows in the application. For example, when a user signs up, a saga may listen to the UserRegisteredEvent and send a welcome email to the user.
Sagas are an extremely powerful feature. A single saga may listen for 1..* events. Using the RxJS library, we can filter, map, fork, and merge event streams to create sophisticated workflows. Each saga returns an Observable which produces a command instance. This command is then dispatched asynchronously by the CommandBus.
Let's create a saga that listens to the HeroKilledDragonEvent and dispatches the DropAncientItemCommand command.
info Hint The
ofTypeoperator and the@Saga()decorator are exported from the@nestjs/cqrspackage.
The @Saga() decorator marks the method as a saga. The events$ argument is an Observable stream of all events. The ofType operator filters the stream by the specified event type. The map operator maps the event to a new command instance.
In this example, we map the HeroKilledDragonEvent to the DropAncientItemCommand command. The DropAncientItemCommand command is then auto-dispatched by the CommandBus.
As with query, command, and event handlers, make sure to register the HeroesGameSagas as a provider in a module:
Unhandled exceptions
Event handlers are executed asynchronously, so they must always handle exceptions properly to prevent the application from entering an inconsistent state. If an exception is not handled, the EventBus will create an UnhandledExceptionInfo object and push it to the UnhandledExceptionBus stream. This stream is an Observable that can be used to process unhandled exceptions.
To filter out exceptions, we can use the ofType operator, as follows:
Where TransactionNotAllowedException is the exception we want to filter out.
The UnhandledExceptionInfo object contains the following properties:
Subscribing to all events
CommandBus, QueryBus and EventBus are all Observables. This means that we can subscribe to the entire stream and, for example, process all events. For example, we can log all events to the console, or save them to the event store.
Request-scoping
For those coming from different programming language backgrounds, it may be surprising to learn that in Nest, most things are shared across incoming requests. This includes a connection pool to the database, singleton services with global state, and more. Keep in mind that Node.js does not follow the request/response multi-threaded stateless model, where each request is processed by a separate thread. As a result, using singleton instances is safe for our applications.
However, there are edge cases where a request-based lifetime for the handler might be desirable. This could include scenarios like per-request caching in GraphQL applications, request tracking, or multi-tenancy. You can learn more about how to control scopes here.
Using request-scoped providers alongside CQRS can be complex because the CommandBus, QueryBus, and EventBus are singletons. Thankfully, the @nestjs/cqrs package simplifies this by automatically creating a new instance of request-scoped handlers for each processed command, query, or event.
To make a handler request-scoped, you can either:
- Depend on a request-scoped provider.
- Explicitly set its scope to
REQUESTusing the@CommandHandler,@QueryHandler, or@EventsHandlerdecorator, as shown:
To inject the request payload into any request-scoped provider, you use the @Inject(REQUEST) decorator. However, the nature of the request payload in CQRS depends on the context—it could be an HTTP request, a scheduled job, or any other operation that triggers a command.
The payload must be an instance of a class extending AsyncContext (provided by @nestjs/cqrs), which acts as the request context and holds data accessible throughout the request lifecycle.
When executing a command, pass the custom request context as the second argument to the CommandBus#execute method:
This makes the MyRequest instance available as the REQUEST provider to the corresponding handler:
You can follow the same approach for queries:
And in the query handler:
For events, while you can pass the request provider to EventBus#publish, this is less common. Instead, use EventPublisher to merge the request provider into a model:
Request-scoped event handlers subscribing to these events will have access to the request provider.
Sagas are always singleton instances because they manage long-running processes. However, you can retrieve the request provider from event objects:
Alternatively, use the request.attachTo(command) method to tie the request context to the command.
Example
A working example is available here.

