Why CQRS and Event Sourcing Are Not "Just for the Elite"?
\n\nWhen we hear the phrases "CQRS" or "Event Sourcing", many JavaScript developers imagine something from the world of enterprise architecture in Java or C#. Complex schemas, endless events, strange storage... But what if I told you that these patterns can (and should!) be used in regular Node.js applications?
\n\nIn this article, we will figure out how CQRS and Event Sourcing help solve real problems: data conflicts, complex logging, scaling, and debugging. And all of this — in pure JavaScript, without magic or complex frameworks.
\n\nWhat is CQRS (Command Query Responsibility Segregation)?
\n\nImagine your application is a restaurant. Waiters (queries) and chefs (commands) work in the same room. Sooner or later they will start getting in each other's way. CQRS suggests separating these zones: waiters work in the hall (reading), and chefs work in the kitchen (writing).
\n\nCQRS is the principle of separating operations into two channels:
\n- \n
- Commands — change the system state (Create, Update, Delete) \n
- Queries — only read data (Read) \n
In a classic REST API, we usually use the same object for reading and writing. For example, UserController contains methods GET /users/:id and POST /users. CQRS suggests separating them into different layers.
Simple CQRS Example in JavaScript
\n\n// Command (state change)\nclass CreateUserCommand {\n constructor(name, email) {\n this.name = name;\n this.email = email;\n }\n}\n\nclass UserCommandHandler {\n async handle(command) {\n // Validation, business logic, saving\n return await db.users.insert({\n name: command.name,\n email: command.email,\n created_at: new Date()\n });\n }\n}\n\n// Query (read only)\nclass GetUserQuery {\n constructor(userId) {\n this.userId = userId;\n }\n}\n\nclass UserQueryHandler {\n async handle(query) {\n // Read only, no changes!\n return await db.users.findOne({ _id: query.userId });\n }\n}\n\nDoes this seem to complicate the code? Yes, at first glance. But when your application grows to 50+ endpoints, separating commands and queries will save you from:
\n- \n
- Accidentally modifying data in GET requests \n
- Caching problems (queries are easy to cache, commands are not) \n
- Conflicts when multiple developers work in parallel \n
Event Sourcing: Remembering Every Action
\n\nNow imagine that instead of storing the current state of a user, you store all events that have happened to them. Not "Ivan, 25 years old, Moscow", but a sequence:
\n- \n
- "User created: Ivan" \n
- "User changed age to 25" \n
- "User moved to Moscow" \n
Event Sourcing is a pattern where every state change is saved as an immutable event. The current state is computed by "replaying" all events.
\n\nImplementing an Event Store in JavaScript
\n\nclass EventStore {\n constructor() {\n this.events = [];\n }\n\n append(aggregateId, event) {\n this.events.push({\n aggregateId,\n event,\n timestamp: new Date()\n });\n }\n\n getEvents(aggregateId) {\n return this.events.filter(e => e.aggregateId === aggregateId);\n }\n}\n\n// Rebuilding state from events\nfunction rebuildState(events) {\n return events.reduce((state, event) => {\n switch(event.event.type) {\n case 'USER_CREATED':\n return { ...state, name: event.event.name, age: 0 };\n case