CQRS and Event Sourcing — TypeScript Implementation

Sanjeev SharmaSanjeev Sharma
2 min read

Advertisement

CQRS separates commands (writes) from queries (reads). Event sourcing stores all changes as immutable events.

Event Sourcing

interface Event {
  type: string;
  data: any;
  timestamp: Date;
}

class EventStore {
  private events: Event[] = [];

  append(event: Event): void {
    this.events.push(event);
  }

  getEvents(id: string): Event[] {
    return this.events.filter((e) => e.data.id === id);
  }

  replay(id: string): any {
    const events = this.getEvents(id);
    let state = {};
    events.forEach((event) => {
      if (event.type === "user:created") {
        state = event.data;
      } else if (event.type === "user:updated") {
        state = { ...state, ...event.data };
      }
    });
    return state;
  }
}

const store = new EventStore();
store.append({ type: "user:created", data: { id: 1, name: "Alice" }, timestamp: new Date() });
store.append({ type: "user:updated", data: { name: "Alice Updated" }, timestamp: new Date() });

const user = store.replay(1);
console.log(user); // { id: 1, name: "Alice Updated" }

CQRS

// Command side (writes)
class CreateUserCommand {
  constructor(private name: string, private email: string) {}
}

class UserCommandHandler {
  execute(command: CreateUserCommand): void {
    const event = {
      type: "user:created",
      data: { id: 1, name: command.name, email: command.email },
      timestamp: new Date(),
    };
    eventStore.append(event);
    messageBus.publish(event);
  }
}

// Query side (reads)
class GetUserQuery {
  constructor(private id: number) {}
}

class UserQueryHandler {
  async execute(query: GetUserQuery): Promise<any> {
    return userReadModel.findById(query.id);
  }
}

FAQ

Q: When to use CQRS? A: Complex domains, audit trails needed, different read/write scales.


CQRS and Event Sourcing enable temporal consistency and auditability.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro