Introduction
As applications grow, managing complex business logic inside a single service or controller becomes difficult. You may start noticing problems like messy code, hard-to-maintain logic, and performance issues.
This is where CQRS (Command Query Responsibility Segregation) comes into the picture.
CQRS is a design pattern that separates "read" operations from "write" operations. When combined with MediatR in .NET, it helps you build clean, scalable, and maintainable applications, especially in microservices architecture.
In this guide, we will understand CQRS in very simple words and learn how to implement it step by step using MediatR in a .NET application.
What is CQRS?
CQRS stands for Command Query Responsibility Segregation.
It means:
Commands → Used to change data (Create, Update, Delete)
Queries → Used to read data (Get, Fetch)
In traditional applications, both read and write logic are mixed together. This makes the system complex.
CQRS separates them into two clear responsibilities.
In simple words:
CQRS separates "reading data" and "changing data" into different paths.
Why Use CQRS?
Using CQRS provides several benefits:
Cleaner code structure
Better separation of concerns
Easier to maintain and test
Improved performance (read and write can scale separately)
It is especially useful in large applications and microservices.
What is MediatR?
MediatR is a lightweight library in .NET that helps you implement the mediator pattern.
Instead of calling services directly, you send requests to a mediator, and it routes them to the correct handler.
In simple words:
MediatR acts like a middleman that connects your request to the right logic.
How CQRS and MediatR Work Together
So instead of controllers doing everything, they just send commands or queries.
Step-by-Step: Implement CQRS with MediatR
Let’s build a simple example.
Step 1: Create a .NET Project
dotnet new webapi -n CQRSExample
cd CQRSExample
Step 2: Install Required Packages
dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
Step 3: Register MediatR in Program.cs
builder.Services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
This enables MediatR in your project.
Step 4: Create a Query (Read Operation)
using MediatR;
public record GetUserQuery(int Id) : IRequest<string>;
This represents a request to get user data.
Step 5: Create Query Handler
public class GetUserQueryHandler : IRequestHandler<GetUserQuery, string>
{
public Task<string> Handle(GetUserQuery request, CancellationToken cancellationToken)
{
return Task.FromResult($"User with ID: {request.Id}");
}
}
This handles the query and returns data.
Step 6: Create a Command (Write Operation)
public record CreateUserCommand(string Name) : IRequest<string>;
This represents a request to create a user.
Step 7: Create Command Handler
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, string>
{
public Task<string> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
return Task.FromResult($"User {request.Name} created successfully");
}
}
This handles data modification.
Step 8: Use MediatR in Controller
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private readonly IMediator _mediator;
public UserController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> Get(int id)
{
var result = await _mediator.Send(new GetUserQuery(id));
return Ok(result);
}
[HttpPost]
public async Task<IActionResult> Create(string name)
{
var result = await _mediator.Send(new CreateUserCommand(name));
return Ok(result);
}
}
Now your controller is very clean and only sends requests.
How This Improves Your Application
Before CQRS:
After CQRS + MediatR:
Logic is separated
Easy to test
Clean architecture
Difference Between Traditional Approach vs CQRS
| Feature | Traditional Approach | CQRS |
|---|
| Structure | Mixed logic | Separated |
| Maintainability | Hard | Easy |
| Scalability | Limited | High |
| Testing | Difficult | Simple |
Best Practices for CQRS in .NET
Keep commands and queries separate
Use meaningful naming
Avoid putting business logic in controllers
Use validation for commands
Keep handlers small and focused
Real-World Use Cases
Conclusion
CQRS with MediatR is a powerful approach for building clean and scalable .NET applications. It helps you separate responsibilities, reduce complexity, and improve maintainability.
Start with small features and gradually apply CQRS to your application. Over time, you will see how much cleaner and easier your code becomes.