Files
pos-system/docs/en/skills/error-handling-patterns.md
Ho Ngoc Hai 9b6c585f57 Enhance documentation structure and improve bilingual support across skills
- Updated skill documentation files to include structured metadata for better organization.
- Enhanced bilingual descriptions and guidelines for clarity in both English and Vietnamese.
- Refined sections on usage, best practices, and related skills to ensure consistency across all documentation.
- Improved formatting and removed outdated references to streamline the documentation experience.
- Added best practices checklists to relevant skills for better usability and adherence to standards.
2026-01-01 07:35:44 +07:00

321 lines
9.4 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 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