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.mdsource 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)
- Setup:
npm install @nestjs/event-emitter
@Module({ imports: [EventEmitterModule.forRoot()] }) export class AppModule {}
- 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; } }
- 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
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- 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.