CQRS and Event Sourcing in TypeScript: Architecture for Complex Systems

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Regular CRUD Applications Stop Working When Business Gets Complex

\n\n

Have you ever encountered a situation where a simple task — "show the order change history" — turns into a nightmare? You dig into the database, look at logs, try to reconstruct who changed the status and when. And then a request comes in: "Create an audit of all user actions for the last three years." And you realize — the current architecture won't handle it.

\n\n

Hi, my name is PythonLib, and today we'll break down two powerful patterns that save the day in such situations: CQRS (Command Query Responsibility Segregation) and Event Sourcing. We'll write code in TypeScript that you can use immediately in a real project.

\n\n

If you develop fintech, marketplaces, CRM, or any systems where history and audit matter — this article is for you.

\n\n

What is CQRS in Simple Terms

\n\n

Imagine a library. Usually, one librarian both checks out books, accepts returns, and keeps records. But when there are many visitors — a queue forms. CQRS proposes separating responsibilities: one librarian only checks out (commands), the second only accepts and records (queries).

\n\n

In programming, this means separating models for reading and writing. Instead of one monolithic repository, we create:

\n\n
    \n
  • Command Model — for changing data (Create, Update, Delete)
  • \n
  • Query Model — for reading data (Read)
  • \n
\n\n

Seems like overcomplication? Yes, at the start. But when the system grows, you gain flexibility: you can optimize reading separately from writing, use different databases, caching.

\n\n

TypeScript Example: Simple CQRS without Event Sourcing

\n\n

Let's say we have an order management system. First, let's define the entities:

\n\n
// domain/order.ts\nexport interface Order {\n  id: string;\n  userId: string;\n  items: OrderItem[];\n  status: 'pending' | 'confirmed' | 'shipped' | 'cancelled';\n  total: number;\n  createdAt: Date;\n  updatedAt: Date;\n}\n\nexport interface OrderItem {\n  productId: string;\n  quantity: number;\n  price: number;\n}\n
\n\n

Now let's create a command and a query:

\n\n
// application/commands/create-order.command.ts\nexport class CreateOrderCommand {\n  constructor(\n    public readonly userId: string,\n    public readonly items: OrderItem[]\n  ) {}\n}\n\n// application/commands/create-order.handler.ts\nimport { CommandHandler, ICommandHandler } from '@nestjs/cqrs';\nimport { OrderRepository } from '../repositories/order.repository';\n\n@CommandHandler(CreateOrderCommand)\nexport class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {\n  constructor(private readonly orderRepo: OrderRepository) {}\n\n  async execute(command: CreateOrderCommand): Promise<string> {\n    const order: Order = {\n      id: uuid(),\n      userId: command.userId,\n      items: command.items,\n      status: 'pending',\n      total: command.items.reduce((sum, item) => sum + item.price * item.quantity, 0),\n      createdAt: new Date(),\n      updatedAt: new Date()\n    };\n    await this.orderRepo.save(order);\n    return order.id;\n  }\n}\n
\n\n

And for reading — a separate handler:

\n\n
// application/queries/get-order.query.ts\nexport class GetOrderQuery {\n  constructor(public readonly orderId: string) {}\n}\n\n// application/queries/get-order.handler.ts\nimport { QueryHandler, IQueryHandler } from '@nestjs/cqrs';\nimport { OrderReadRepository } from '../repositories/order-read.repository';\n\n@QueryHandler(GetOrderQuery)\nexport class GetOrderHandler implements IQueryHandler<GetOrderQuery> {\n  constructor(private readonly readRepo: OrderReadRepository) {}\n\n  async execute(query: GetOrderQuery): Promise<Order | null> {\n    return this.readRepo.findById(query.orderId);\n  }\n}\n                

Blogs

Book Recommendations