> For the complete documentation index, see [llms.txt](https://docs.soapjs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.soapjs.com/components/cqrs-and-events.md).

# CQRS & Events

SoapJS ships first-class **CQRS** — commands, queries and domain events — wired through your DI container by decorators. It's **opt-in**: enable it with `bootstrap({ cqrs: true })`. For simple features you can skip it entirely and use a plain `controller → use case → repository` flow.

* **Commands** change state (create/update/delete) and return a `Result`.
* **Queries** read state and return a `Result`.
* **Domain events** are facts ("something happened") that any number of consumers can react to.

### Commands

Define a command (a message) and a handler:

```typescript
import { BaseCommand } from '@soapjs/soap/cqrs';
import { CommandHandler } from '@soapjs/soap-express/cqrs';
import { Inject, Result } from '@soapjs/soap';

export class CreateUserCommand extends BaseCommand {
  constructor(public readonly email: string) { super(); }
}

@CommandHandler(CreateUserCommand)
export class CreateUserHandler {
  constructor(@Inject('UserRepository') private readonly repo: UserRepository) {}

  async handle(cmd: CreateUserCommand): Promise<Result<User>> {
    // ...validate, build the entity, persist
    return Result.withSuccess(user);
  }
}
```

### Queries

```typescript
import { BaseQuery } from '@soapjs/soap/cqrs';
import { QueryHandler } from '@soapjs/soap-express/cqrs';

export class GetUserQuery extends BaseQuery<User | null> {
  constructor(public readonly id: string) { super(); }
}

@QueryHandler(GetUserQuery)
export class GetUserHandler {
  constructor(@Inject('UserRepository') private readonly repo: UserRepository) {}
  async handle(query: GetUserQuery): Promise<Result<User | null>> {
    return this.repo.findById(query.id);
  }
}
```

### Wiring & dispatching

With `cqrs: true`, `bootstrap()` binds a `CommandBus` and a `QueryBus` in the container and registers every decorated handler. **Handlers register when their files are imported**, so make sure they're loaded (e.g. imported in your composition root) before bootstrap runs.

```typescript
import { CommandBus, QueryBus } from '@soapjs/soap/cqrs';

@Controller('/users')
export class UsersController {
  constructor(
    @Inject('CommandBus') private readonly commandBus: CommandBus,
    @Inject('QueryBus') private readonly queryBus: QueryBus,
  ) {}

  @Post('/')
  async create(req: Request, res: Response): Promise<void> {
    const result = await this.commandBus.dispatch(new CreateUserCommand(req.body.email));
    ResultMapper.toResponse(result, res, { successStatus: 201 });
  }
}
```

```typescript
await bootstrap({ controllers: [UsersController], container, cqrs: true });
```

### Domain Events

Define events as facts (extend `BaseDomainEvent`) and react with `@EventHandler`:

```typescript
import { BaseDomainEvent } from '@soapjs/soap/domain';
import { EventHandler, IEventHandler } from '@soapjs/soap-express/cqrs';

export class UserCreatedEvent extends BaseDomainEvent<{ id: string; email: string }> {
  constructor(user: User) {
    super('user.created', user.id, { id: user.id, email: user.email });
  }
}

@EventHandler(UserCreatedEvent)
export class SendWelcomeEmail implements IEventHandler<UserCreatedEvent> {
  async handle(event: UserCreatedEvent): Promise<void> {
    // ...send the email
  }
}
```

A command handler publishes events after it succeeds (inject your event bus and call `publish`).

> **Multiple handlers per event (fan-out).** Since `@soapjs/soap-express` 0.3.1 the default DI token is `EventHandler:<eventName>:<handlerClass>`, so several handlers can subscribe to the **same** event without overwriting each other. Pass `{ token }` to override it.

> **Events are not auto-wired.** `bootstrap({ cqrs: true })` wires the command and query **buses**, but `@EventHandler` only *records* consumers in `DecoratorRegistry` — it does not connect them to a bus. You wire the event bus yourself (this is intentional: you choose in-memory, Kafka, RabbitMQ, etc.).

A minimal in-memory wiring, mirroring `wireCqrs`:

```typescript
import { DecoratorRegistry } from '@soapjs/soap-express';

export function wireEvents(container: DIContainer, bus: InMemoryEventBus): void {
  DecoratorRegistry.getEventHandlers().forEach((meta) => {
    if (!container.has(meta.token)) container.bindClass(meta.token, meta.handlerClass);
    bus.subscribe(meta.eventType, container.get(meta.token));
  });
}
```

### Read models & projections

Domain events are the natural way to keep a **read model** in sync: a projector consumes events and updates a denormalized store; queries read the projection instead of aggregating the write store on every request. See the [demo](https://github.com/soapjs/soap-node-demo) for a complete commands → events → projection → WebSocket flow.

### Guidelines

* One handler per command/query; **many** handlers per event.
* Keep handlers thin — orchestrate the domain, return a `Result`.
* Commands read-before-write on the primary for strong consistency; route read queries to a replica if you use a read/write split.
