Mastering the CQRS Pattern in NestJS

Arunachalam kalimuthu
5 min readSep 15, 2024

Use Cases, Project Setup, Endpoints, and Test Cases

CQRS (Command and Query Responsibility Segregation) is an architectural pattern that separates read and write operations into distinct models. This separation allows for optimized scalability, flexibility, and maintainability in large-scale applications. In this blog post, we will walk you through implementing CQRS in a NestJS project, covering use cases, project setup, sample endpoints, and test cases.

Why CQRS?

The CQRS pattern helps address several common challenges in growing applications:

  1. Separation of Concerns: By separating read and write models, you create cleaner code with a clear boundary between operations.
  2. Scalability: Read and write operations can be scaled independently.
  3. Flexibility: You can use different data stores or different models for reading and writing.
  4. Improved Performance: Optimized data stores and access patterns for queries and commands can lead to performance gains.

Use Cases for CQRS

Let’s explore the most common use cases for CQRS in a real-world scenario.

  1. Create an Entity (Command): When a user sends data to create a new resource.
  2. Fetch Entities (Query): When a user retrieves a list of resources or a single resource.
  3. Update an Entity (Command): When a user modifies an existing resource.
  4. Delete an Entity (Command): When a user removes a resource from the system.

Project Setup

We’ll use NestJS to set up a simple CQRS-based project. To get started, run the following commands:

npm install -g @nestjs/cli
nest new cqrs-app
cd cqrs-app
npm install @nestjs/cqrs

Next, we’ll set up the basic project structure:

src/
├── app.module.ts
├── blog/
│ ├── commands/
│ │ └── create-post.command.ts
│ ├── queries/
│ │ └── get-posts.query.ts
│ ├── handlers/
│ │ ├── create-post.handler.ts
│ │ └── get-posts.handler.ts
│ ├── controllers/
│ │ └── blog.controller.ts
│ ├── dto/
│ │ └── create-post.dto.ts
│ └── blog.module.ts
└── main.ts

Step 1: Defining the Command and Query

In the CQRS pattern, commands are used for writing (create, update, delete), while queries are used for reading (fetch data).

Create a Post Command

The command initiates the process of creating a blog post. Create create-post.command.ts under the commands folder:

export class CreatePostCommand {
constructor(
public readonly title: string,
public readonly content: string,
public readonly authorId: number
) {}
}

Get Posts Query

The query retrieves a list of posts. Create get-posts.query.ts under the queries folder:

export class GetPostsQuery {}

Step 2: Create Command and Query Handlers

Handlers process commands and queries. Command handlers change the state of the system, while query handlers fetch data.

Create Post Command Handler

Create the file create-post.handler.ts in the handlers folder:

import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { CreatePostCommand } from '../commands/create-post.command';

@CommandHandler(CreatePostCommand)
export class CreatePostHandler implements ICommandHandler<CreatePostCommand> {
async execute(command: CreatePostCommand): Promise<void> {
const { title, content, authorId } = command;
// Logic for creating a post
console.log(`Creating post with title: ${title}, content: ${content}, authorId: ${authorId}`);
// Save post to the database (mock)
}
}

Get Posts Query Handler

Create get-posts.handler.ts under the handlers folder:

import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { GetPostsQuery } from '../queries/get-posts.query';
@QueryHandler(GetPostsQuery)
export class GetPostsHandler implements IQueryHandler<GetPostsQuery> {
async execute(): Promise<any[]> {
// Fetch posts from the database (mock data for now)
return [
{ id: 1, title: 'First Post', content: 'Content of the first post' },
{ id: 2, title: 'Second Post', content: 'Content of the second post' }
];
}
}

Step 3: Setting Up the Controller

The controller handles incoming HTTP requests and delegates the work to command and query handlers. Create the blog.controller.ts file in the controllers folder:

import { Body, Controller, Get, Post } from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { CreatePostCommand } from '../commands/create-post.command';
import { GetPostsQuery } from '../queries/get-posts.query';
import { CreatePostDto } from '../dto/create-post.dto';

@Controller('posts')
export class BlogController {
constructor(private commandBus: CommandBus, private queryBus: QueryBus) {}

@Post()
async createPost(@Body() createPostDto: CreatePostDto) {
const { title, content, authorId } = createPostDto;
await this.commandBus.execute(new CreatePostCommand(title, content, authorId));
return { message: 'Post created successfully!' };
}

@Get()
async getPosts() {
return this.queryBus.execute(new GetPostsQuery());
}
}

Step 4: Setting Up DTOs

Data Transfer Objects (DTOs) are used to define the structure of the data that is sent between the client and the server. Create create-post.dto.ts under the dto folder:

export class CreatePostDto {
title: string;
content: string;
authorId: number;
}

Step 5: Configuring the Blog Module

Finally, set up the blog.module.ts file to register the command and query handlers. This file ties everything together:

import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { BlogController } from './controllers/blog.controller';
import { CreatePostHandler } from './handlers/create-post.handler';
import { GetPostsHandler } from './handlers/get-posts.handler';
@Module({
imports: [CqrsModule],
controllers: [BlogController],
providers: [CreatePostHandler, GetPostsHandler]
}
)

export class BlogModule {}

Step 6: Application Module

Update the app.module.ts to import the BlogModule:

import { Module } from '@nestjs/common';
import { BlogModule } from './blog/blog.module';

@Module({
imports: [BlogModule]
}
)

export class AppModule {}

Step 7: Sample Test Cases

Now that you’ve set up the endpoints, you can write test cases for your CQRS commands and queries.

Create Post Command Test

describe('CreatePostHandler', () => {
it('should create a post', async () => {
const command = new CreatePostCommand('Test Post', 'This is a test post', 1);
const handler = new CreatePostHandler();
await handler.execute(command);
expect(console.log).toHaveBeenCalledWith('Creating post with title: Test Post, content: This is a test post, authorId: 1');
});
});

Get Posts Query Test

describe('GetPostsHandler', () => {
it('should return posts', async () => {
const handler = new GetPostsHandler();
const result = await handler.execute();
expect(result).toEqual([
{ id: 1, title: 'First Post', content: 'Content of the first post' },
{ id: 2, title: 'Second Post', content: 'Content of the second post' }
]);
});
});

Conclusion

The CQRS pattern allows you to separate concerns, improve scalability, and enhance performance in your application. By separating commands (write operations) and queries (read operations), you create a more maintainable and flexible system. In this blog post, we’ve covered how to set up a CQRS-based NestJS project, created sample endpoints, and written basic test cases.

CQRS is a powerful architecture pattern for applications where scalability and flexibility are priorities, and it integrates seamlessly with NestJS.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response