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

9.4 KiB

name, description
name description
error-handling-patterns 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:

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:

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

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:

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:

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:

{
  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