Stop Conflating CQRS and MediatR: They Are Not the Same Thing
CQRS and MediatR are two different ideas. Learn what each one really does, why people mix them up, and how to use CQRS in .NET with or without MediatR.
Two different tools in the same toolbox
Think about your kitchen at home.
A knife cuts vegetables. A gas stove heats food. Both live in the same kitchen. You often use them together to cook one meal. But they are not the same thing. A knife does not heat food. A stove does not cut onions.
Now imagine someone says, "A knife is cooking." That sounds silly, right? A knife is just one tool you use while cooking. You can cook without that exact knife. You can use the knife for things that are not cooking at all.
This is exactly what happens with CQRS and MediatR in the .NET world.
Many developers say "CQRS" but they really mean "MediatR." They have used them together so many times that the two words got glued in their heads. But CQRS is the idea, like cooking. MediatR is just one tool, like the knife. You can have one without the other.
Let us slowly pull them apart so you can see each one clearly.
What CQRS really is
CQRS stands for Command Query Responsibility Segregation. That is a big phrase. Let us break it down.
- A command is when you want to change something. "Place this order." "Update my address." "Delete this comment."
- A query is when you want to read something. "Show my orders." "What is my balance?" "List the top books."
- Segregation just means keep them apart.
So CQRS says one simple thing: the code that changes data should be separate from the code that reads data.
That is it. CQRS is not a library. You cannot install CQRS from NuGet. It is a way of thinking about your code.
Here is the shape of the idea.
Why would you want this split? Because reads and writes have different needs.
When you write, you care about rules. Is the order valid? Does the user have enough money? You want safety and care.
When you read, you care about speed. You just want to show data on the screen fast. You often do not need the full, heavy model with all its rules.
By keeping the two sides apart, each side can be as simple or as smart as it needs to be.
CQRS does not mean two databases
A common myth: "CQRS means I need two databases." No. Most teams use one normal database.
CQRS is mainly about splitting reads and writes in your code. Using a separate read database is an advanced add-on. It is useful for very large systems, often together with event sourcing. But you do not need it to say you are "doing CQRS."
Levels of CQRS
Steps
Split classes
Command and query classes are separate. One database.
Split models
Write model has rules. Read model is a thin DTO.
Split storage
Separate read database, kept in sync. Advanced only.
What MediatR really is
Now meet the other tool.
MediatR is a small .NET library. It is an implementation of the Mediator design pattern. Its job is to sit in the middle of your app and pass messages around so that classes do not have to call each other directly.
Here is how it works in plain words:
- You create a small request object that holds some data.
- You send that request through MediatR.
- MediatR finds the one handler that knows how to deal with that request.
- The handler does the work and returns a result.
The caller never sees the handler. It only knows about MediatR. This is called decoupling. The pieces are loosely connected.
Notice something important: nothing here says anything about reads or writes. MediatR does not care if your request changes data or reads data. It just delivers the message. You could use MediatR for sending emails, for logging, for anything.
So MediatR is a delivery system. CQRS is a way to organize. They are doing two different jobs.
A tiny MediatR example
Here is what a query looks like when you use MediatR.
// The request: a small message asking for data.
public record GetOrderById(int OrderId) : IRequest<OrderDto>;
// The handler: the one place that knows how to answer this request.
public class GetOrderByIdHandler : IRequestHandler<GetOrderById, OrderDto>
{
private readonly AppDbContext _db;
public GetOrderByIdHandler(AppDbContext db) => _db = db;
public async Task<OrderDto> Handle(GetOrderById request, CancellationToken ct)
{
var order = await _db.Orders
.Where(o => o.Id == request.OrderId)
.Select(o => new OrderDto(o.Id, o.Total))
.FirstOrDefaultAsync(ct);
return order!;
}
}And the controller just sends the message:
[ApiController]
[Route("orders")]
public class OrdersController : ControllerBase
{
private readonly IMediator _mediator;
public OrdersController(IMediator mediator) => _mediator = mediator;
// Route is GET /orders/{id} (wrapped in code so MDX does not choke on braces)
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var result = await _mediator.Send(new GetOrderById(id));
return Ok(result);
}
}The controller does not know which class handles the request. It only talks to MediatR. That is the mediator pattern at work.
Why people mix them up
So if they are different, why do so many people glue them together?
The reason is simple: they fit nicely together. MediatR has a request and a handler. CQRS has a command and a query. A command can be a MediatR request. A query can be a MediatR request. The shapes match.
Because of this neat fit, almost every CQRS tutorial in .NET uses MediatR. New developers read those tutorials, see the two always together, and decide they are the same thing. The habit spreads.
Here is the truth side by side.
| Question | CQRS | MediatR |
|---|---|---|
| What is it? | An architectural idea | A .NET library |
| Main job | Separate reads from writes | Pass a message to one handler |
| Can you install it? | No, it is a way of thinking | Yes, from NuGet |
| Cares about reads vs writes? | Yes, that is its whole point | No, it does not care |
| Needed for the other? | No | No |
Read that last row again. Neither one needs the other. That is the key idea of this whole post.
The four honest combinations
Steps
Both
Split reads and writes, deliver them through MediatR.
CQRS only
Split reads and writes, call handlers directly.
MediatR only
Use the mediator, but do not separate reads and writes.
Neither
A small app with plain services. Totally fine.
CQRS without MediatR
Let us prove the point. We can do clean CQRS with zero libraries. We keep our command and query classes separate, and we call their handlers directly through the dependency injection container.
First, define small interfaces of our own:
// Our own tiny contracts. No external library needed.
public interface ICommand<TResult> { }
public interface IQuery<TResult> { }
public interface ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
Task<TResult> Handle(TCommand command, CancellationToken ct);
}
public interface IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
Task<TResult> Handle(TQuery query, CancellationToken ct);
}Now a query and its handler look almost the same as before, but with our interfaces:
public record GetOrderById(int OrderId) : IQuery<OrderDto>;
public class GetOrderByIdHandler : IQueryHandler<GetOrderById, OrderDto>
{
private readonly AppDbContext _db;
public GetOrderByIdHandler(AppDbContext db) => _db = db;
public async Task<OrderDto> Handle(GetOrderById query, CancellationToken ct)
{
return await _db.Orders
.Where(o => o.Id == query.OrderId)
.Select(o => new OrderDto(o.Id, o.Total))
.FirstOrDefaultAsync(ct) ?? throw new KeyNotFoundException();
}
}The controller injects the handler directly:
[HttpGet("{id}")]
public async Task<IActionResult> Get(
int id,
IQueryHandler<GetOrderById, OrderDto> handler,
CancellationToken ct)
{
var result = await handler.Handle(new GetOrderById(id), ct);
return Ok(result);
}Look closely. We still have commands and queries kept apart. That is real CQRS. But there is no MediatR anywhere. The reads still live in query handlers. The writes would live in command handlers. The split is complete.
This diagram shows how the request flows when you own the dispatch:
If you have many handlers and you do not want to inject each one by hand, you can write a small dispatcher of your own. It is usually under fifty lines. That gives you the "send a message" feel of MediatR while keeping full control and no license cost.
When MediatR is worth it (and when it is not)
MediatR is not bad. It is a fine tool. The mistake is using it without thinking, just because a tutorial did.
Here is an honest guide.
| Situation | Good fit for MediatR? |
|---|---|
| You want pipeline behaviors for logging, validation, caching | Yes, this is its strength |
| You have a large app with hundreds of handlers | Often yes, less wiring |
| You have a small app with a few endpoints | Usually no, it adds noise |
| You want to learn how dispatch really works | No, build your own first |
| Licensing for a big company is a concern | Weigh the cost carefully |
The biggest real benefit of MediatR is its pipeline behaviors. These let you wrap every request with shared steps, like validation or logging, in one place. If you lean on those a lot, MediatR earns its keep. If you do not, you may be paying complexity for nothing.
The licensing change you should know about
In July 2025, MediatR (and AutoMapper) moved to a commercial license under a new company, Lucky Penny Software, created by the original author Jimmy Bogard.
There is a free Community edition for individuals, learning, non-profits, and small companies under a revenue limit. Bigger companies usually need a paid plan, priced by team size. Older MIT-licensed versions still exist and are archived, but new versions follow the commercial terms.
This is not a reason to panic. But it is a reason to be honest about whether you truly need MediatR. If you were only using it because "that is how you do CQRS," now you know better. You can do CQRS without it.
A clear decision path
Here is a simple way to choose, step by step.
Walk it slowly:
- Is your app tiny with just a few endpoints? Use plain services. You may not need CQRS or MediatR at all.
- Will your app grow and have clear read and write paths? Then split them. That is CQRS.
- Once you have CQRS, ask: do you need shared pipeline steps like validation on every request? If yes, MediatR is a good fit. If no, write a small dispatcher you own.
Notice that CQRS comes before the MediatR question. That is the right order. First decide how to organize. Then pick the delivery tool.
Common mistakes to avoid
A few traps that students fall into:
- Calling MediatR "CQRS." Now you know they are different. Use the right word.
- Adding MediatR to a tiny app. If you have five endpoints, the extra layers just make the code harder to follow.
- Thinking CQRS needs two databases. It does not. One database is the normal case.
- Putting business rules in query handlers. Queries should mostly just read. Keep the heavy rules on the command side.
- Using MediatR only to feel "clean." Cleanliness comes from clear separation, not from one library.
Quick recap
- CQRS is an idea: keep the code that changes data separate from the code that reads data.
- MediatR is a library: it passes a request to exactly one handler. It is the mediator pattern.
- They fit together well, so people glued them in their minds. But they are not the same thing.
- You can do CQRS without MediatR using your own small interfaces or a tiny dispatcher.
- You can use MediatR without CQRS, because MediatR does not care about reads versus writes.
- MediatR's strongest feature is its pipeline behaviors for shared steps like validation and logging.
- MediatR went commercial in July 2025 under Lucky Penny Software, with a free Community tier and paid plans for larger companies.
- Decide your architecture first (CQRS or not), then pick your delivery tool (MediatR, your own dispatcher, or direct calls).
References and further reading
- Milan Jovanovic: Stop Conflating CQRS and MediatR
- Microsoft Learn: CQRS and the application layer with MediatR
- Jimmy Bogard: AutoMapper and MediatR Commercial Editions Launch Today
- Code Maze: CQRS and MediatR in ASP.NET Core
- Cezary Piatek: Why I don't use MediatR for CQRS
Related Patterns
CQRS Pattern with MediatR in .NET: A Friendly Guide
Learn the CQRS pattern with MediatR in .NET using simple words, clear diagrams, and real C# code. Beginner friendly, with pitfalls and licensing notes.
CQRS Pattern in .NET: The Way It Should Have Been From the Start
A friendly, hands-on guide to the CQRS pattern in .NET 10. Learn commands, queries, handlers, diagrams, and when to actually use it.
Refactoring a Modular Monolith Without MediatR in .NET
Learn to remove MediatR from a .NET modular monolith using plain handlers and a tiny dispatcher, with CQRS, pipeline behaviors, and clear module boundaries.
CQRS Validation with MediatR Pipeline and FluentValidation in .NET
Learn centralized CQRS validation in .NET using a MediatR pipeline behavior and FluentValidation. Simple words, clear diagrams, and real C# code.
Event Sourcing for .NET Developers: A Simple Introduction
Learn event sourcing in .NET from scratch. Store every change as an event instead of just the current state, with a real-life bank-passbook analogy, diagrams, code, aggregates, projections, and when to use it.
12 Essential Distributed System Design Patterns Every Architect Should Know
A friendly guide to 12 distributed system design patterns in .NET — saga, CQRS, outbox, circuit breaker, retry, sidecar, and more, with diagrams and code.