Files
pos-system/docs/en/skills/error-handling-patterns.md
Ho Ngoc Hai 2640b351c3 Enhance documentation with detailed diagrams and structured flows
- Added request/response flow diagrams to api-design and api-gateway-advanced skills for better visualization of processes.
- Introduced configuration loading flow in configuration-management skill to clarify the configuration process.
- Included error propagation flow in error-handling-patterns skill to illustrate error handling across layers.
- Enhanced various skills with additional diagrams to improve understanding of complex concepts.

These updates aim to provide clearer guidance and improve the overall documentation experience for developers.
2026-01-01 23:22:54 +07:00

461 lines
13 KiB
Markdown

---
name: error-handling-patterns
description: Error handling patterns and conventions for GoodGo microservices. Use when implementing error handling, creating custom error classes, handling exceptions, standardizing error responses, or debugging error scenarios.
---
# 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 Propagation Flow
The following diagram illustrates how errors propagate through the application layers:
```mermaid
flowchart TD
A[Request] --> B[Controller]
B --> C[Service Layer]
C --> D[Repository Layer]
D --> E{Error Occurs?}
E -->|Yes| F[Throw HttpError]
E -->|No| G[Return Data]
F --> H[Error Middleware]
H --> I{Error Type?}
I -->|HttpError| J[Extract Status/Code]
I -->|Prisma Error| K[Map to HttpError]
I -->|Zod Error| L[Map to ValidationError]
I -->|Unknown| M[Map to InternalServerError]
J --> N[Log Error]
K --> N
L --> N
M --> N
N --> O{Is Operational?}
O -->|Yes| P[Log as Warning]
O -->|No| Q[Log as Error]
P --> R[Format Response]
Q --> R
R --> S{Is Production?}
S -->|Yes & 5xx| T[Generic Message]
S -->|No or < 5xx| U[Detailed Message]
T --> V[Send Response]
U --> V
G --> V
```
### Error Hierarchy Structure
The error class hierarchy shows the relationship between different error types:
```mermaid
classDiagram
class Error {
<<built-in>>
+message: string
+stack: string
}
class HttpError {
+statusCode: number
+errorCode: string
+isOperational: boolean
+details?: any
+toApiResponse()
}
class NotFoundError {
+statusCode: 404
}
class BadRequestError {
+statusCode: 400
}
class ValidationError {
+statusCode: 422
}
class UnauthorizedError {
+statusCode: 401
}
class ForbiddenError {
+statusCode: 403
}
class ConflictError {
+statusCode: 409
}
class RateLimitError {
+statusCode: 429
}
class InternalServerError {
+statusCode: 500
}
class ServiceUnavailableError {
+statusCode: 503
}
class DatabaseError {
+statusCode: 500
}
class ExternalServiceError {
+statusCode: 502
}
Error <|-- HttpError
HttpError <|-- NotFoundError
HttpError <|-- BadRequestError
HttpError <|-- ValidationError
HttpError <|-- UnauthorizedError
HttpError <|-- ForbiddenError
HttpError <|-- ConflictError
HttpError <|-- RateLimitError
HttpError <|-- InternalServerError
HttpError <|-- ServiceUnavailableError
HttpError <|-- DatabaseError
HttpError <|-- ExternalServiceError
```
### Error Handling Decision Tree
Use this decision tree to determine which error class to use:
```mermaid
flowchart TD
A[Error Occurs] --> B{Error Type?}
B -->|Resource Not Found| C[NotFoundError<br/>404]
B -->|Invalid Input| D{Validation?}
B -->|Authentication| E{Type?}
B -->|Resource Conflict| F[ConflictError<br/>409]
B -->|Rate Limit| G[RateLimitError<br/>429]
B -->|Database| H[DatabaseError<br/>500]
B -->|External Service| I[ExternalServiceError<br/>502]
B -->|Service Unavailable| J[ServiceUnavailableError<br/>503]
B -->|Unknown/Programming| K[InternalServerError<br/>500]
D -->|Schema Validation| L[ValidationError<br/>422]
D -->|Bad Request Format| M[BadRequestError<br/>400]
E -->|No Token/Invalid| N[UnauthorizedError<br/>401]
E -->|No Permission| O[ForbiddenError<br/>403]
C --> P[Set isOperational: true]
L --> P
M --> P
N --> P
O --> P
F --> P
G --> P
H --> Q{Is Operational?}
I --> Q
J --> Q
K --> R[Set isOperational: false]
Q -->|Yes| P
Q -->|No| R
P --> S[Include Error Code]
R --> S
S --> T[Add Context Details]
T --> U[Throw Error]
```
### 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.
## 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