--- trigger: always_on --- # 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