From harness-claude
Implements event-driven patterns in NestJS using EventEmitter2 for in-process pub/sub and CQRS with CommandBus and QueryBus for separating read/write models.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:nestjs-event-drivenThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Build event-driven systems with EventEmitter2, CQRS module, CommandBus, and QueryBus
Build event-driven systems with EventEmitter2, CQRS module, CommandBus, and QueryBus
npm install @nestjs/event-emitter
@Module({ imports: [EventEmitterModule.forRoot()] })
export class AppModule {}
@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;
}
}
@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> { ... }
}
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.
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.
https://docs.nestjs.com/recipes/cqrs
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeGuides implementing CQRS to separate read and write models, optimize query performance, and build event-sourced systems.
Guides implementing CQRS pattern with TypeScript: separate read/write models, command handlers with Prisma, denormalized read models for complex queries.
Designs and implements CQRS patterns for scalable systems, covering logical separation, separate read models, event-sourced CQRS, and query optimization.