Harness-engineering nestjs-event-driven

NestJS Event-Driven Pattern

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/nestjs-event-driven" ~/.claude/skills/intense-visions-harness-engineering-nestjs-event-driven-bd2de8 && rm -rf "$T"
manifest: agents/skills/codex/nestjs-event-driven/SKILL.md
source content

NestJS Event-Driven Pattern

Build event-driven systems with EventEmitter2, CQRS module, CommandBus, and QueryBus

When to Use

  • You need to decouple side effects (send email, update audit log) from the primary operation
  • You are implementing CQRS to separate read and write models for a bounded context
  • You need event sourcing where domain events are the source of truth
  • You want to publish domain events that multiple services can independently handle

Instructions

EventEmitter2 (simple pub/sub within the same process)

  1. Setup:
npm install @nestjs/event-emitter
@Module({ imports: [EventEmitterModule.forRoot()] })
export class AppModule {}
  1. Emit events from a service:
@Injectable()
export class UsersService {
  constructor(private eventEmitter: EventEmitter2) {}

  async create(dto: CreateUserDto): Promise<User> {
    const user = await this.prisma.user.create({ data: dto });
    this.eventEmitter.emit('user.created', new UserCreatedEvent(user));
    return user;
  }
}
  1. Handle events:
@Injectable()
export class EmailNotificationListener {
  @OnEvent('user.created')
  async handleUserCreated(event: UserCreatedEvent): Promise<void> {
    await this.mailService.sendWelcomeEmail(event.user.email);
  }

  @OnEvent('user.*') // wildcard
  async handleAnyUserEvent(event: unknown): Promise<void> { ... }
}

CQRS Module (commands and queries)

npm install @nestjs/cqrs
// command
export class CreateUserCommand { constructor(public dto: CreateUserDto) {} }

// handler
@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
  constructor(private usersRepo: UsersRepository) {}
  async execute(command: CreateUserCommand): Promise<User> {
    return this.usersRepo.create(command.dto);
  }
}

// dispatch from controller
@Post()
create(@Body() dto: CreateUserDto) {
  return this.commandBus.execute(new CreateUserCommand(dto));
}

// query
@QueryHandler(GetUserQuery)
export class GetUserHandler implements IQueryHandler<GetUserQuery> {
  async execute(query: GetUserQuery): Promise<User> {
    return this.usersReadRepo.findById(query.id);
  }
}

Register all handlers in

providers
.

Details

EventEmitter2 vs CQRS: EventEmitter2 is in-process pub/sub — simple, zero setup overhead, good for side effects. CQRS is a structural pattern that separates commands (writes) from queries (reads) — use it when you need independent scaling of read/write paths or when the read model differs significantly from the write model.

EventEmitter2 async handlers:

@OnEvent('user.created', { async: true })
ensures async handlers do not block the event loop. Set
promisify: true
on
EventEmitterModule.forRoot()
to await async handlers before continuing.

EventBus (CQRS): In addition to

CommandBus
and
QueryBus
, the CQRS module provides
EventBus
for domain events.
IEventHandler
implementations react to events published via
eventBus.publish(new UserCreatedEvent(user))
.

Saga pattern:

@nestjs/cqrs
supports long-running processes via
@Saga()
decorator — these are RxJS streams that react to events and dispatch commands.

Cross-service events: EventEmitter2 is in-process only. For cross-service messaging (microservices), use the NestJS microservices transport layer (

@EventPattern
, Redis/RabbitMQ) or integrate with Kafka/NATS for durable event streams.

Source

https://docs.nestjs.com/recipes/cqrs

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.