/nestjs-cqrx

EventStoreDB NestJS CQRS module

Primary LanguageTypeScriptMIT LicenseMIT

nestjs-cqrx

EventStoreDB NestJS CQRS module.

Based on

Features

  • Asynchronous commit/publish
  • Event handler decorator
  • Single event for read/write

Install

npm install --save nestjs-cqrx

Usage

import { CqrxModule } from 'nestjs-cqrx';

@Module({
  imports: [
    CqrxModule.forRoot({
      eventstoreDbConnectionString: 'esdb://localhost:2113?tls=false',
    }),
  ],
})
export class AppModule {}

You can generate connection string on Connection details page

Example of User model

import { ConflictException } from '@nestjs/common';
import { AggregateRoot, EventHandler } from 'nestjs-cqrx';

import { UserRegistered } from '../events';

export class User extends AggregateRoot {
  protected static readonly streamName: string = 'user';
  isRegistered = false;
  email!: string;
  password!: string;

  @EventHandler(UserRegistered)
  createUser(event: UserRegistered): void {
    this.isRegistered = true;
    this.email = event.data.email;
    this.password = event.data.password;
  }

  register(email: string, password: string) {
    if (this.isRegistered) {
      throw new ConflictException();
    }

    this.apply(
      new UserRegistered({
        email,
        password,
      }),
    );
  }
}

Example of usage

const user = new User('123');
user.apply(new UserRegistered({ data }));
await userAggregateRepository.save(user);
// Or you can create aggregate from repository
// In this case you can use commit method
const user = userAggregateRepository.create('123');
user.apply(new UserRegistered({ data }));
await user.commit();

Example of events

import { Event } from 'nestjs-cqrx';

type UserRegisteredDto = { email: string; password: string };

export class UserRegistered extends Event<UserRegisteredDto> {}
@Module({
  imports: [
    CqrxModule.forFeature(
      [User],
      // Subscribe and transform events from eventstore
      [['UserRegistered', event => new UserRegistered(event)]],
    ),
  ],
})
export class UserModule {}
// Signature of transformers
type Transformer = [
  /* Recorded event type */ string,
  /* Function which accept stream event (plain object) */ (
    event: RecordedEvent,
  ) => Event,
];

['UserRegistered', event => new UserRegistered(event)] can be shorthanded to UserRegistered

Note: If you have decorator EventsHandler (from @nestjs/cqrs) of some event, it will be automatically added to transform service.

Example apps pros/cons

example / example-tick-tack-toe-cqrx

[+] good option (save event to db, subscribe to event from db)
[–] synchronous (we must wait when event will be saved then reply to client)

[+] official nestjs/cqrs implementation, command handlers (fire new command via saga)
[+] faster, we reply processing to client, and do command on
[–] can emit only 1 event from saga

Similar Projects

Development

Resources

Todo

  • read from specific position
  • find lib for creating errors
  • better to split on read/write events
  • reducer (similar to evolve of emmet)

License

MIT License (c) 2024