# CQRS Handler Error Handling Guide
## GoodGo Platform Implementation Standards

---

## 📌 Quick Reference

| Status | Count | Modules |
|--------|-------|---------|
| ✓ Has Error Handling | 11 | auth (5), listings (2), admin, notifications, payments, search |
| ✗ Needs Error Handling | 66 | All other handlers + 6 auth handlers |
| **Total** | **77** | **All modules** |

---

## 🎯 Pattern 1: Standard Command Handler with Error Handling

Use this pattern for **most command handlers**:

```typescript
import { Logger } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
import { DomainException, InternalServerErrorException } from '@modules/shared';

@CommandHandler(YourCommand)
export class YourCommandHandler implements ICommandHandler<YourCommand> {
  private readonly logger = new Logger(this.constructor.name);

  constructor(
    private readonly repository: IYourRepository,
    private readonly eventBus: EventBus,
  ) {}

  async execute(command: YourCommand): Promise<YourResult> {
    try {
      // Load aggregate
      const aggregate = await this.repository.findById(command.id);
      if (!aggregate) {
        throw new NotFoundException('Aggregate', command.id);
      }

      // Execute domain logic
      aggregate.doSomething(command.data);

      // Save state
      await this.repository.save(aggregate);

      // Publish events
      const events = aggregate.clearDomainEvents();
      for (const event of events) {
        this.eventBus.publish(event);
      }

      return { id: aggregate.id, status: 'success' };
    } catch (error) {
      // Always re-throw domain exceptions
      if (error instanceof DomainException) throw error;

      // Log unexpected errors
      this.logger.error(
        `Command execution failed: ${error instanceof Error ? error.message : String(error)}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );

      // Throw generic HTTP exception
      throw new InternalServerErrorException('Operation failed, please try again');
    }
  }
}
```

---

## 🎯 Pattern 2: Standard Query Handler with Error Handling

Use this pattern for **all query handlers**:

```typescript
import { Logger } from '@nestjs/common';
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { InternalServerErrorException } from '@modules/shared';

@QueryHandler(YourQuery)
export class YourQueryHandler implements IQueryHandler<YourQuery> {
  private readonly logger = new Logger(this.constructor.name);

  constructor(private readonly repository: IYourRepository) {}

  async execute(query: YourQuery): Promise<YourQueryResult> {
    try {
      const result = await this.repository.find(query.criteria);
      if (!result) {
        throw new NotFoundException('Data not found');
      }
      return result;
    } catch (error) {
      this.logger.error(
        `Query execution failed: ${error instanceof Error ? error.message : String(error)}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Unable to fetch data, please try again');
    }
  }
}
```

---

## 🎯 Pattern 3: Bulk Operation with Per-Item Error Handling

Use this pattern for **batch operations** (like `bulk-moderate-listings`):

```typescript
@CommandHandler(BulkCommand)
export class BulkCommandHandler implements ICommandHandler<BulkCommand> {
  private readonly logger = new Logger(this.constructor.name);

  async execute(command: BulkCommand): Promise<BulkResult> {
    const succeeded: string[] = [];
    const failed: Array<{ id: string; reason: string }> = [];

    try {
      for (const id of command.ids) {
        try {
          const item = await this.repository.findById(id);
          if (!item) {
            failed.push({ id, reason: 'Item not found' });
            continue;
          }

          // Process item
          item.update(command.data);
          await this.repository.save(item);
          
          succeeded.push(id);
        } catch (itemError) {
          const message = itemError instanceof Error ? itemError.message : 'Unknown error';
          failed.push({ id, reason: message });
          // Continue processing other items
        }
      }

      return { succeeded, failed, processed: command.ids.length };
    } catch (error) {
      this.logger.error(
        `Bulk operation failed: ${error instanceof Error ? error.message : String(error)}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Batch operation failed');
    }
  }
}
```

---

## 🎯 Pattern 4: Graceful Degradation (Non-Critical Services)

Use this pattern when **secondary services can fail without blocking** the main operation (like duplicate detection in `create-listing`):

```typescript
async execute(command: CreateListingCommand): Promise<CreateListingResult> {
  try {
    // Critical path - must succeed
    const listing = await this.createListing(command);
    await this.repository.save(listing);

    // Non-critical: Duplicate detection
    let duplicates = [];
    try {
      duplicates = await this.duplicateDetector.find({
        title: command.title,
        location: command.location,
      });
    } catch (error) {
      this.logger.warn(
        'Duplicate detection failed, continuing without warnings',
        'CreateListingHandler'
      );
      // Continue - duplicates are optional
    }

    // Non-critical: Price validation
    let priceWarning: PriceWarning | undefined;
    try {
      const result = await this.priceValidator.validate(command);
      if (result.isSuspicious) {
        priceWarning = result;
      }
    } catch (error) {
      this.logger.warn(
        'Price validation failed, continuing without warning',
        'CreateListingHandler'
      );
      // Continue - price warning is optional
    }

    return {
      listingId: listing.id,
      duplicates,
      priceWarning,
    };
  } catch (error) {
    if (error instanceof DomainException) throw error;
    this.logger.error(
      `Create listing failed: ${error instanceof Error ? error.message : String(error)}`,
      error instanceof Error ? error.stack : undefined,
      'CreateListingHandler'
    );
    throw new InternalServerErrorException('Failed to create listing');
  }
}
```

---

## 🎯 Pattern 5: Authorization/Authentication Errors

Use this pattern when **authentication can fail** (like `login-user`):

```typescript
@CommandHandler(LoginUserCommand)
export class LoginUserHandler implements ICommandHandler<LoginUserCommand> {
  private readonly logger = new Logger(this.constructor.name);

  constructor(private readonly tokenService: TokenService) {}

  async execute(command: LoginUserCommand): Promise<TokenPair> {
    try {
      return await this.tokenService.generateTokenPair({
        sub: command.userId,
        phone: command.phone,
        role: command.role,
      });
    } catch (error) {
      this.logger.error(
        `Token generation failed for user ${command.userId}: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        'LoginUserHandler'
      );
      // Use specific exception for authentication
      throw new UnauthorizedException('Unable to create session, please try again');
    }
  }
}
```

---

## ❌ Common Mistakes to Avoid

### ❌ Mistake 1: Silent Catch Block
```typescript
// BAD ❌
try {
  await this.repository.save(entity);
} catch (error) {
  // Silent - no logging, no error thrown
}
```

**Fix:**
```typescript
// GOOD ✓
try {
  await this.repository.save(entity);
} catch (error) {
  this.logger.error(`Save failed: ${error}`, error?.stack, 'HandlerName');
  throw new InternalServerErrorException('Save failed');
}
```

---

### ❌ Mistake 2: Swallowing Domain Exceptions
```typescript
// BAD ❌
try {
  const result = entity.validate();
  if (result.isErr) {
    throw result.unwrapErr(); // ValidationException
  }
  // ...
} catch (error) {
  // This swallows the validation error
  throw new InternalServerErrorException('Failed');
}
```

**Fix:**
```typescript
// GOOD ✓
try {
  const result = entity.validate();
  if (result.isErr) {
    throw result.unwrapErr();
  }
  // ...
} catch (error) {
  // Re-throw domain exceptions
  if (error instanceof DomainException) throw error;
  
  this.logger.error(`Unexpected: ${error}`, error?.stack);
  throw new InternalServerErrorException('Failed');
}
```

---

### ❌ Mistake 3: Logging to Console
```typescript
// BAD ❌
try {
  // ...
} catch (error) {
  console.error(error); // Not structured, not captured by logging system
  throw error;
}
```

**Fix:**
```typescript
// GOOD ✓
try {
  // ...
} catch (error) {
  this.logger.error(
    `Error: ${error instanceof Error ? error.message : String(error)}`,
    error instanceof Error ? error.stack : undefined,
    this.constructor.name,
  );
  throw new InternalServerErrorException('Operation failed');
}
```

---

### ❌ Mistake 4: Partial Logging
```typescript
// BAD ❌
try {
  // ...
} catch (error) {
  this.logger.error(error.message); // Missing stack trace!
  throw error;
}
```

**Fix:**
```typescript
// GOOD ✓
try {
  // ...
} catch (error) {
  this.logger.error(
    error instanceof Error ? error.message : String(error),
    error instanceof Error ? error.stack : undefined,
    this.constructor.name
  );
  throw error;
}
```

---

### ❌ Mistake 5: Publishing Events Before Confirming Success
```typescript
// BAD ❌
try {
  aggregate.apply(command);
  this.eventBus.publish(aggregate.events); // Before save!
  await this.repository.save(aggregate);
} catch (error) {
  // Event published but entity not saved!
}
```

**Fix:**
```typescript
// GOOD ✓
try {
  aggregate.apply(command);
  await this.repository.save(aggregate); // Save first
  
  // Publish events only after successful save
  const events = aggregate.clearDomainEvents();
  for (const event of events) {
    this.eventBus.publish(event);
  }
} catch (error) {
  // No events published - data is consistent
}
```

---

## 🔍 Audit Checklist for Each Handler

When reviewing error handling, verify:

- [ ] **Try-Catch Block**: Wraps entire execute method logic
- [ ] **Domain Exception Re-throw**: `if (error instanceof DomainException) throw error;`
- [ ] **Error Logging**: Includes message, stack trace, and context
- [ ] **Logger Usage**: Uses injected logger, not console
- [ ] **Appropriate Exception**: Throws correct HTTP exception type
- [ ] **No Silent Catches**: Every catch block has logging or throw
- [ ] **Event Publishing**: Only after successful state persistence
- [ ] **Transaction Rollback**: Handled by try-catch (implicit or explicit)
- [ ] **User Message**: Meaningful error response (not technical details)
- [ ] **Logging Context**: Includes handler class name

---

## 📋 Implementation Checklist for Handlers Needing Error Handling

1. **Review existing pattern** in well-implemented handlers:
   - `auth/commands/login-user` (auth patterns)
   - `listings/commands/create-listing` (graceful degradation)
   - `admin/commands/bulk-moderate-listings` (batch operations)

2. **Add to execute method**:
   ```typescript
   async execute(command: YourCommand): Promise<YourResult> {
     try {
       // existing logic
     } catch (error) {
       // error handling
     }
   }
   ```

3. **Check if domain exception should be re-thrown**:
   ```typescript
   if (error instanceof DomainException) throw error;
   ```

4. **Add appropriate logging**:
   ```typescript
   this.logger.error(
     `Message: ${error instanceof Error ? error.message : String(error)}`,
     error instanceof Error ? error.stack : undefined,
     this.constructor.name
   );
   ```

5. **Throw appropriate HTTP exception**:
   ```typescript
   throw new InternalServerErrorException();
   // or
   throw new NotFoundException();
   // or
   throw new BadRequestException();
   ```

6. **Test error scenarios**:
   - Repository returns null/error
   - Domain validation fails
   - Database save fails
   - Event publishing fails

---

## 🚀 Priority Implementation Order

### TIER 1 - Do First (33 handlers)
- **admin**: 14 handlers
- **leads**: 5 handlers
- **inquiries**: 4 handlers
- **reviews**: 5 handlers
- **subscriptions**: 5 handlers

**Effort**: ~2 developer-days

### TIER 2 - Do Second (18 handlers)
- **payments**: 4 handlers
- **search**: 8 handlers
- **listings**: 5 handlers (2 already done)
- **agents**: 3 handlers

**Effort**: ~1 developer-day

### TIER 3 - Do Last (8 handlers)
- **analytics**: 8 handlers
- **auth**: 6 handlers (5 already done)

**Effort**: ~1 developer-day

---

## 📞 FAQ

**Q: Should every handler have error handling?**  
A: Yes. Every async operation can fail - databases go down, networks fail, etc.

**Q: Should I catch and swallow domain exceptions?**  
A: No. Re-throw them using `if (error instanceof DomainException) throw error;`

**Q: What if the handler has no database calls?**  
A: Still add error handling. External service calls, calculations, and I/O all need try-catch.

**Q: Can I use generic Exception handling?**  
A: No. Import and use NestJS exceptions like `InternalServerErrorException`, `NotFoundException`, etc.

**Q: Should I log to console?**  
A: No. Inject and use the NestJS Logger service for structured logging.

**Q: What about promise rejection handling?**  
A: Async/await with try-catch handles all promise rejections. That's why we wrap the entire execute method.

---

## 🎓 Reference: Exemplary Handlers

### 1. **Login User Handler** (Excellent)
Location: `apps/api/src/modules/auth/application/commands/login-user/login-user.handler.ts`

✓ Clear error message for user  
✓ Proper exception type (UnauthorizedException)  
✓ Stack trace logged  
✓ Handler context included  

### 2. **Create Listing Handler** (Advanced)
Location: `apps/api/src/modules/listings/application/commands/create-listing/create-listing.handler.ts`

✓ Critical path is protected  
✓ Non-critical services can degrade gracefully  
✓ Continues operation even if secondary services fail  
✓ Warnings still provided when possible  

### 3. **Bulk Moderate Handler** (Batch Pattern)
Location: `apps/api/src/modules/admin/application/commands/bulk-moderate-listings/bulk-moderate-listings.handler.ts`

✓ Per-item error collection  
✓ Processing continues for other items  
✓ Complete result returned with failures  
✓ Individual error tracking  

---

**Last Updated**: April 11, 2026  
**Audit Coverage**: 77 handlers across 12 modules  
**Compliance**: 14.3% currently implemented
