Skip to main content

Command Watcher

The Command Watcher tracks CQRS command execution in NestJS applications, monitoring command handling, performance, and results.

What Gets Captured

  • Command name (class name)
  • Handler name
  • Execution status (executing, completed, failed)
  • Processing duration
  • Command payload
  • Command result
  • Error messages
  • Command metadata

Configuration

NestLensModule.forRoot({
watchers: {
command: {
enabled: true,
capturePayload: true,
captureResult: true,
maxPayloadSize: 64 * 1024, // 64KB
},
},
})

Configuration Options

OptionTypeDefaultDescription
enabledbooleanfalseEnable/disable command tracking
capturePayloadbooleantrueCapture command payload
captureResultbooleantrueCapture command result
maxPayloadSizenumber64 * 1024Max payload size (bytes)

Payload Structure

interface CommandEntry {
type: 'command';
payload: {
name: string; // Command class name
handler?: string; // Handler name
status: 'executing' | 'completed' | 'failed';
duration?: number; // Execution time (ms)
payload?: unknown; // Command data
result?: unknown; // Command result
error?: string; // Error message
metadata?: Record<string, unknown>; // Command metadata
};
}

Usage Example

Setup CQRS Module

// Install: npm install @nestjs/cqrs
import { CqrsModule } from '@nestjs/cqrs';

@Module({
imports: [CqrsModule],
})
export class AppModule {}

Provide CommandBus to NestLens

import { CommandBus } from '@nestjs/cqrs';
import { NESTLENS_COMMAND_BUS } from 'nestlens';

@Module({
providers: [
{
provide: NESTLENS_COMMAND_BUS,
useExisting: CommandBus,
},
],
})
export class AppModule {}

Define Commands

export class CreateUserCommand {
constructor(
public readonly email: string,
public readonly name: string,
public readonly password: string,
) {}
}

export class UpdateUserCommand {
constructor(
public readonly userId: string,
public readonly data: Partial<User>,
) {}
}

export class DeleteUserCommand {
constructor(public readonly userId: string) {}
}

Command Handlers

import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';

@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
constructor(private userRepository: UserRepository) {}

async execute(command: CreateUserCommand): Promise<User> {
// Automatically tracked
const user = await this.userRepository.create({
email: command.email,
name: command.name,
password: command.password,
});

return user;
}
}

@CommandHandler(UpdateUserCommand)
export class UpdateUserHandler implements ICommandHandler<UpdateUserCommand> {
async execute(command: UpdateUserCommand): Promise<User> {
return await this.userRepository.update(
command.userId,
command.data,
);
}
}

Executing Commands

@Controller('users')
export class UserController {
constructor(private commandBus: CommandBus) {}

@Post()
async create(@Body() dto: CreateUserDto) {
// Command execution is tracked
const command = new CreateUserCommand(
dto.email,
dto.name,
dto.password,
);
return await this.commandBus.execute(command);
}

@Put(':id')
async update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
const command = new UpdateUserCommand(id, dto);
return await this.commandBus.execute(command);
}

@Delete(':id')
async remove(@Param('id') id: string) {
const command = new DeleteUserCommand(id);
return await this.commandBus.execute(command);
}
}

Dashboard View

In the NestLens dashboard, command entries show:

  • Command execution timeline
  • Most executed commands
  • Slow commands
  • Failed commands with errors
  • Command execution patterns
  • Average duration per command type

Command Metadata

Add metadata to track additional context:

export class CreateOrderCommand {
constructor(
public readonly userId: string,
public readonly items: OrderItem[],
// Metadata fields
public readonly timestamp = new Date(),
public readonly correlationId = uuid(),
) {}
}

Error Handling

@CommandHandler(ProcessPaymentCommand)
export class ProcessPaymentHandler implements ICommandHandler<ProcessPaymentCommand> {
async execute(command: ProcessPaymentCommand) {
try {
return await this.paymentGateway.charge(command);
} catch (error) {
// Error tracked automatically
throw new PaymentFailedException(error.message);
}
}
}

Command Validation

@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
async execute(command: CreateUserCommand) {
// Validation failures are tracked
if (!command.email || !command.name) {
throw new BadRequestException('Missing required fields');
}

return await this.userRepository.create(command);
}
}