CQRS vs traditional CRUD — when does each fit?
Pick by workload complexity and team size, not by tech fashion.
Side-by-side
| Factor | CQRS | Traditional CRUD |
|---|---|---|
| Code structure | Commands + queries + handlers | Controllers + services + repositories |
| File count for a CRUD endpoint | 4-6 (cmd, handler, validator, query, query-handler, DTO) | 1-2 (controller method + service method) |
| Read DTO freedom | Independent of write entity | Often the entity itself or close variants |
| Indirection | Mediator dispatch — 1 click to handler in IDE | Direct method call — instant trace |
| Business rules placement | Forced into command handler | Anywhere in the service layer |
| Scaling reads independently | Easy — separate read model | Hard — same model |
| Cross-cutting concerns | Pipeline behaviors, one place | Filters / interceptors / decorators, harder to compose |
| Best for | 20+ endpoints, complex business rules | Simple CRUD apps |
Decision rule
Pick CQRS when
- The app has distinct write and read profiles (10-100x more reads than writes)
- Multiple complex business operations with real invariants ("close month-end", "approve loan", "transfer funds")
- Many different read shapes for the same data (list, detail, dashboard, export, report)
- Audit / regulatory requirements — every state change should be a named, logged command
- Team size 5+ — the structure forces good boundaries, eases onboarding
- Multiple input channels (HTTP + queue consumer + cron) hitting the same business logic
Pick CRUD when
- Simple internal admin tool
- Single dev maintaining a side project
- Pre-product-market-fit MVP
- Backend whose job is "store + retrieve JSON" with minimal logic
- Prototype you'll throw away
A pragmatic middle ground
You can use CQRS in modular form without going all-in:
- Plain controllers for trivial CRUD (
GET /users) - MediatR + command handlers for complex business operations (
POST /orders/place,POST /loans/approve)
This "CQRS-lite" approach gives you the benefit where it pays without the file ceremony for simple endpoints.
Common mistake
Adopting CQRS for the entire codebase on day one. You'll generate 4 files for GET /products that returns a flat list. Resist. Start small, expand where business logic warrants.
Interview-grade summary
"CQRS is the right tool when your read and write paths have genuinely different concerns — different scaling, different DTOs, different business rules around them. Traditional CRUD is the right tool when those concerns overlap and the file ceremony of CQRS would add complexity without benefit. Most production systems end up using both — CQRS for complex business operations, CRUD for simple lookup endpoints."