---
name: error-handling-patterns
description: Error handling patterns for GoodGo services. Use for custom error classes, exception handling, standardized error responses, or debugging.
compatibility: "typescript>=5, zod>=3"
---

# Error Handling Patterns

## When to Use This Skill

Use this skill when:
- Implementing error handling in services, controllers, or repositories
- Creating custom error classes for specific error scenarios
- Standardizing error responses across APIs
- Handling exceptions from external services or database operations
- Implementing error middleware and global error handlers
- Debugging error scenarios and improving error messages
- Distinguishing between operational and programming errors

## Core Concepts

### Error Types

1. **Operational Errors**: Expected errors that occur during normal operation
   - Examples: Validation errors, authentication failures, resource not found
   - Should be handled gracefully and return appropriate HTTP status codes
   - Safe to expose error details to clients (with caution)

2. **Programming Errors**: Unexpected errors due to bugs in code
   - Examples: Null pointer exceptions, type errors, logic bugs
   - Should be logged with full details for debugging
   - Should return generic error messages to clients (hide implementation details)

### Error Code System

The platform uses a centralized error code system (`ErrorCode` enum) that:
- Provides unique identifiers for each error type
- Maps to HTTP status codes consistently
- Enables error tracking and analytics
- Supports internationalization

Error codes follow the pattern: `{CATEGORY}_{NUMBER}`
- `AUTH_001` - Authentication errors
- `VALIDATION_001` - Validation errors
- `RESOURCE_001` - Resource errors
- `DB_001` - Database errors

## Patterns

### Base Error Class: HttpError

All custom errors extend the `HttpError` base class:

```typescript
export class HttpError extends Error {
  public readonly statusCode: number;
  public readonly errorCode: string;
  public readonly isOperational: boolean;
  public readonly details?: any;

  constructor(
    message: string,
    statusCode: number = 500,
    errorCode: string = 'INTERNAL_ERROR',
    isOperational: boolean = true,
    details?: any
  ) {
    super(message);
    this.statusCode = statusCode;
    this.errorCode = errorCode;
    this.isOperational = isOperational;
    this.details = details;
    Error.captureStackTrace(this, this.constructor);
  }

  toApiResponse() {
    return {
      success: false,
      error: {
        code: this.errorCode,
        message: this.message,
        ...(this.details && { details: this.details }),
      },
      timestamp: new Date().toISOString(),
    };
  }
}
```

### Standard Error Classes

Use these predefined error classes for common scenarios:

**Resource Errors:**
- `NotFoundError` - 404: Resource not found
- `ConflictError` - 409: Resource conflict (e.g., duplicate)

**Validation Errors:**
- `ValidationError` - 422: Input validation failed
- `BadRequestError` - 400: Invalid request

**Authentication/Authorization:**
- `UnauthorizedError` - 401: Authentication required
- `ForbiddenError` - 403: Access denied

**System Errors:**
- `InternalServerError` - 500: Internal server error (programming error)
- `ServiceUnavailableError` - 503: Service temporarily unavailable
- `DatabaseError` - 500: Database operation failed
- `ExternalServiceError` - 502: External service error

**Rate Limiting:**
- `RateLimitError` - 429: Too many requests

### Error Code Enum

Centralized error codes in `ErrorCode` enum:

```typescript
export enum ErrorCode {
  // Authentication & Authorization
  UNAUTHORIZED = 'AUTH_001',
  FORBIDDEN = 'AUTH_002',
  INVALID_TOKEN = 'AUTH_003',
  TOKEN_EXPIRED = 'AUTH_004',
  
  // Validation
  VALIDATION_ERROR = 'VALIDATION_001',
  INVALID_FORMAT = 'VALIDATION_002',
  
  // Resources
  NOT_FOUND = 'RESOURCE_001',
  ALREADY_EXISTS = 'RESOURCE_002',
  CONFLICT = 'RESOURCE_003',
  
  // Database
  DATABASE_ERROR = 'DB_001',
  CONSTRAINT_VIOLATION = 'DB_004',
  
  // System
  INTERNAL_ERROR = 'SYS_001',
  RATE_LIMIT_EXCEEDED = 'SYS_003',
}
```

### Using Errors in Services

```typescript
import { NotFoundError, ConflictError } from '../errors/http-error';
import { ErrorCode } from '../errors/error-codes';

export class UserService {
  async getUserById(id: string) {
    const user = await this.repository.findById(id);
    
    if (!user) {
      throw new NotFoundError('User', { id });
    }
    
    return user;
  }
  
  async createUser(data: CreateUserInput) {
    const existing = await this.repository.findByEmail(data.email);
    
    if (existing) {
      throw new ConflictError('User with this email already exists');
    }
    
    return await this.repository.create(data);
  }
}
```

### Error Middleware Pattern

Global error handler middleware processes all errors:

```typescript
export const errorHandler = (
  err: any,
  req: express.Request,
  res: express.Response,
  _next: express.NextFunction
): void => {
  let statusCode = 500;
  let errorCode = ErrorCode.INTERNAL_ERROR;
  let message = 'Internal server error';
  let isOperational = false;
  
  // Handle HttpError instances
  if (err instanceof HttpError) {
    statusCode = err.statusCode;
    errorCode = err.errorCode as ErrorCode;
    message = err.message;
    isOperational = err.isOperational;
  }
  // Handle Prisma errors
  else if (err.code === 'P2002') {
    statusCode = 409;
    errorCode = ErrorCode.CONSTRAINT_VIOLATION;
    message = 'Resource already exists';
    isOperational = true;
  }
  // Handle Zod validation errors
  else if (err.name === 'ZodError') {
    statusCode = 422;
    errorCode = ErrorCode.VALIDATION_ERROR;
    message = 'Validation failed';
    // Extract validation details
  }
  
  // Log error
  if (!isOperational || statusCode >= 500) {
    logger.error('Unhandled error', { error: err, statusCode, errorCode });
  } else {
    logger.warn('Operational error', { error: err, statusCode, errorCode });
  }
  
  // Send response
  const response = {
    success: false,
    error: {
      code: errorCode,
      message: isProduction && statusCode >= 500 
        ? 'Internal server error' 
        : message,
    },
    timestamp: new Date().toISOString(),
  };
  
  res.status(statusCode).json(response);
};
```

### Async Error Wrapper

Wrap async route handlers to catch promise rejections:

```typescript
export const asyncHandler = (fn: Function) => {
  return (req: express.Request, res: express.Response, next: express.NextFunction) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

// Usage
router.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await userService.getUserById(req.params.id);
  res.json({ success: true, data: user });
}));
```

### Error Response Format

Standardized error response format:

```typescript
{
  success: false,
  error: {
    code: "RESOURCE_001",
    message: "User not found",
    details?: {
      // Optional additional details (not in production for 5xx errors)
    }
  },
  timestamp: "2024-01-01T00:00:00.000Z"
}
```

## Best Practices

1. **Use Specific Error Classes**: Use the most specific error class available
2. **Include Context**: Provide helpful error messages with context
3. **Mark Operational Errors**: Set `isOperational: true` for expected errors
4. **Don't Expose Internal Details**: Hide implementation details in production
5. **Log Appropriately**: Use `logger.error()` for programming errors, `logger.warn()` for operational errors
6. **Handle Database Errors**: Map Prisma errors to appropriate HTTP errors
7. **Use Error Codes**: Always use `ErrorCode` enum for consistency
8. **Validate Early**: Validate input early to catch errors before processing

## Common Mistakes

1. **Not Using Error Classes**: Using generic `Error` instead of specific error classes
2. **Exposing Stack Traces**: Including stack traces in production responses
3. **Ignoring Errors**: Not handling errors in async operations
4. **Generic Error Messages**: Using vague error messages without context
5. **Not Logging**: Forgetting to log errors for debugging
6. **Wrong HTTP Status Codes**: Using incorrect status codes for error types
7. **Not Using Error Middleware**: Handling errors manually instead of using middleware

## Troubleshooting

### Error Not Caught by Middleware

**Problem**: Error not being caught by error middleware
**Solution**: Ensure error middleware is added last, after all routes. Use `asyncHandler` for async route handlers.

### Generic Error Messages in Production

**Problem**: Generic "Internal server error" shown even for operational errors
**Solution**: Check `isOperational` flag is set correctly. Verify error middleware handles all error types.

### Error Code Not Found

**Problem**: Error code not in `ErrorCode` enum
**Solution**: Add error code to enum following naming convention. Update `ERROR_CODE_TO_STATUS` mapping.

### Stack Traces Exposed

**Problem**: Stack traces visible in API responses
**Solution**: Ensure production environment checks are in place. Use error middleware to filter stack traces.

## Quick Reference

| Error Class | Status | When to Use |
|-------------|--------|-------------|
| `NotFoundError` | 404 | Resource not found |
| `BadRequestError` | 400 | Invalid request |
| `ValidationError` | 422 | Input validation failed |
| `UnauthorizedError` | 401 | Auth required |
| `ForbiddenError` | 403 | Access denied |
| `ConflictError` | 409 | Duplicate/conflict |
| `RateLimitError` | 429 | Too many requests |
| `InternalServerError` | 500 | Server error |

**Error Response Format:**
```typescript
{
  success: false,
  error: {
    code: "RESOURCE_001",
    message: "User not found",
    details?: { /* validation details */ }
  },
  timestamp: "2024-01-01T00:00:00.000Z"
}
```

**Common Error Codes:**
| Code | Category | Description |
|------|----------|-------------|
| `AUTH_001` | Auth | Unauthorized |
| `AUTH_003` | Auth | Invalid token |
| `VALIDATION_001` | Validation | Validation failed |
| `RESOURCE_001` | Resource | Not found |
| `RESOURCE_002` | Resource | Already exists |
| `DB_001` | Database | Database error |
| `SYS_001` | System | Internal error |

**Essential Imports:**
```typescript
import { NotFoundError, ValidationError, ConflictError } from '../errors/http-error';
import { ErrorCode } from '../errors/error-codes';
import { asyncHandler } from '../middlewares/async.middleware';
```

## Resources

- [Error Classes](../../services/iam-service/src/errors/http-error.ts) - Base error classes
- [Error Codes](../../services/iam-service/src/errors/error-codes.ts) - Error code definitions
- [Error Middleware](../../services/iam-service/src/middlewares/error.middleware.ts) - Global error handler
- [API Design](../api-design/SKILL.md) - API response formats
- [Security](../security/SKILL.md) - Security error handling
