Skip to content

Error Handling in TicketHive

Muhammad Saqib edited this page Nov 24, 2025 · 1 revision

Error Handling in TicketHive

This document describes the error handling architecture and patterns used throughout the TicketHive application.

Overview

TicketHive implements a structured, type-safe error handling system that:

  • Provides user-friendly error messages
  • Maintains consistent error responses across the API
  • Distinguishes between business logic errors and infrastructure errors
  • Handles database-level errors gracefully
  • Supports proper HTTP status codes and error categorization

Error Architecture

Three-Layer Error Handling

The application uses a three-layer approach to error handling:

  1. Database Layer (PostgreSQL)

    • Throws low-level technical errors with PostgreSQL error codes
    • Examples: 57014 (statement timeout), 23505 (unique constraint violation)
  2. Service Layer (src/services/)

    • Catches database errors and converts them to business-friendly AppError instances
    • Throws AppError for business logic violations
    • Examples: EVENT_NOT_FOUND, EVENT_SOLD_OUT
  3. Route Layer (src/routes/)

    • Catches all errors and formats them as HTTP responses
    • Uses handleError function for consistent error formatting
    • Maps errors to appropriate HTTP status codes

Error Categories

Business Logic Errors (4xx - Client Errors)

These are expected errors caused by invalid user requests or business rule violations. They indicate the user did something wrong or tried to do something not allowed.

Error Code Status Meaning User Action
EVENT_NOT_FOUND 404 Event doesn't exist Check event ID
EVENT_SOLD_OUT 409 No tickets available Try different event
BOOKING_ALREADY_CANCELLED 409 Already cancelled Don't retry
INVALID_CREDENTIALS 401 Wrong email/password Check credentials
EMAIL_ALREADY_REGISTERED 409 Email taken Use different email
INVALID_TOKEN 401 Invalid or expired token Re-authenticate
UNAUTHORIZED 403 Unauthorized access Check permissions

Characteristics:

  • Part of normal operation
  • Should NOT trigger alerts
  • User can fix by changing their request
  • Should NOT be retried (same request will fail again)
  • Logged for analytics but not as errors

Infrastructure Errors (5xx - Server Errors)

These indicate system problems - not user mistakes. Something is wrong with the infrastructure (database, network, etc.) or the application itself.

Error Code Status Meaning User Action
STATEMENT_TIMEOUT 503 Database overloaded Wait and retry
DATABASE_CONNECTION_ERROR 503 Can't reach database Wait and retry
INTERNAL_SERVER_ERROR 500 Unexpected bug Report issue
FAILED_TO_CREATE_EVENT 500 Event creation failed Report issue
FAILED_TO_CREATE_USER 500 User creation failed Report issue

Characteristics:

  • Exceptional and should be rare
  • Should trigger monitoring alerts
  • Not the user's fault
  • CAN be retried (may succeed on retry)
  • Logged as errors for investigation
  • May indicate need for scaling/optimization

Core Components

Error Codes

All application errors are defined in src/lib/errors.ts using a centralized ErrorCode object:

export const ErrorCode = {
  // Authentication errors
  INVALID_CREDENTIALS: "INVALID_CREDENTIALS",
  EMAIL_ALREADY_REGISTERED: "EMAIL_ALREADY_REGISTERED",
  INVALID_TOKEN: "INVALID_TOKEN",
  UNAUTHORIZED: "UNAUTHORIZED",

  // Booking errors
  EVENT_NOT_FOUND: "EVENT_NOT_FOUND",
  EVENT_SOLD_OUT: "EVENT_SOLD_OUT",
  BOOKING_NOT_FOUND: "BOOKING_NOT_FOUND",
  BOOKING_ALREADY_CANCELLED: "BOOKING_ALREADY_CANCELLED",

  // Event errors
  FAILED_TO_CREATE_EVENT: "FAILED_TO_CREATE_EVENT",
  FAILED_TO_CREATE_USER: "FAILED_TO_CREATE_USER",

  // Database timeout errors
  STATEMENT_TIMEOUT: "STATEMENT_TIMEOUT",
  DATABASE_CONNECTION_ERROR: "DATABASE_CONNECTION_ERROR",

  // Unknown/generic errors
  INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR",
} as const;

AppError Class

The custom AppError class extends the standard Error with additional properties:

export class AppError extends Error {
  public code: ErrorCode;
  public statusCode: number;

  constructor(
    code: ErrorCode,
    statusCode: number = ERROR_METADATA[code].statusCode,
    message?: string,
  ) {
    super(message || ERROR_METADATA[code].message);
    this.code = code;
    this.statusCode = statusCode;
    this.name = "AppError";
  }

  static isAppError(error: unknown): error is AppError {
    return error instanceof AppError;
  }

  getStatusCode(): number {
    return this.statusCode;
  }
}

Error Metadata

Each error code maps to HTTP status codes and user-friendly messages:

export const ERROR_METADATA: Record<
  ErrorCode,
  { statusCode: number; message: string }
> = {
  [ErrorCode.INVALID_CREDENTIALS]: {
    statusCode: 401,
    message: "Invalid email or password",
  },
  [ErrorCode.EVENT_NOT_FOUND]: {
    statusCode: 404,
    message: "Event not found",
  },
  // ... more mappings
};

Database Error Handling

PostgreSQL Error Detection

The system includes helper functions to detect specific PostgreSQL errors:

  • isPostgresTimeoutError(error) - Detects statement timeouts (code 57014)
  • isPostgresUniqueConstraintError(error) - Detects unique constraint violations (code 23505)
  • isPostgresForeignKeyViolationError(error) - Detects foreign key violations (code 23503)
  • isConnectionTimeoutError(error) - Detects connection timeouts (code ETIMEDOUT)

Database Configuration

The database connection in src/lib/db.ts is configured with:

  • Connection Pool: 20 max connections, 30s idle timeout
  • Connect Timeout: 10 seconds for initial connection
  • Statement Timeout: 5 seconds for query execution

This configuration prevents indefinite lock waits and provides fast failure under high contention.

Error Response Format

All error responses follow a consistent JSON format:

{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "User-friendly error message"
  }
}

Examples

Business Logic Error:

{
  "success": false,
  "error": {
    "code": "EVENT_SOLD_OUT",
    "message": "Event is sold out"
  }
}

Infrastructure Error:

{
  "success": false,
  "error": {
    "code": "STATEMENT_TIMEOUT",
    "message": "High traffic detected. Please try again in a moment."
  }
}

Validation Error:

{
  "success": false,
  "error": {
    "code": "INVALID_CREDENTIALS",
    "message": "Validation failed"
  },
  "details": [
    {
      "code": "invalid_type",
      "expected": "string",
      "received": "undefined",
      "path": ["email"],
      "message": "Required"
    }
  ]
}

Usage Patterns

Throwing Errors in Services

Services should throw AppError instances for business logic violations:

// Check if event exists
if (events.length === 0) {
  throw new AppError(ErrorCode.EVENT_NOT_FOUND);
}

// Check ticket availability
if (event.available_tickets <= 0) {
  throw new AppError(ErrorCode.EVENT_SOLD_OUT);
}

Handling Database Errors

Services should catch and convert database errors to appropriate AppError instances:

try {
  // Database operation that might fail
  const result = await sql`...`;
} catch (error) {
  if (isPostgresUniqueConstraintError(error)) {
    throw new AppError(ErrorCode.EMAIL_ALREADY_REGISTERED);
  }
  if (isPostgresForeignKeyViolationError(error)) {
    throw new AppError(ErrorCode.EVENT_NOT_FOUND);
  }
  // Let other database errors propagate to route handler
  throw error;
}

Route-Level Error Handling

Routes should use the centralized handleError function:

import { handleError } from "../lib/errorHandler.ts";

export async function createBooking(req: Request, res: Response) {
  try {
    // Business logic
    const booking = await bookingService.createBooking(bookingData);
    res.status(201).json({
      success: true,
      data: booking,
      message: "Booking created successfully"
    });
  } catch (error) {
    handleError(error, res, "createBooking");
  }
}

Best Practices

1. User-Friendly Messages

Never expose technical error details to end users. Convert technical errors to business-friendly messages.

❌ Technical:

PostgresError: canceling statement due to statement timeout

✅ User-Friendly:

{
  "code": "STATEMENT_TIMEOUT",
  "message": "High traffic detected. Please try again in a moment."
}

2. Consistent Error Codes

Use the centralized ErrorCode object instead of string literals to ensure consistency.

3. Proper HTTP Status Codes

  • 4xx: User/client errors (business logic violations)
  • 5xx: Server/infrastructure errors (system problems)
  • 503: Temporary unavailability (retry recommended)

4. Logging Strategy

  • Log business logic errors at INFO level for analytics
  • Log infrastructure errors at ERROR level for investigation
  • Include context in error logs for debugging

5. Retry Strategy

  • Do retry: Infrastructure errors (503 status)
  • Don't retry: Business logic errors (4xx status)

Monitoring and Alerting

Monitor These Metrics

  • Error Rate: Percentage of requests that result in errors
  • Error Distribution: Breakdown by error code and category
  • Timeout Rate: Percentage of database timeouts
  • Response Time: P95 and P99 response times

Alert On

  • High rate of infrastructure errors (>5%)
  • Database connection pool exhaustion
  • Statement timeout rate >20% (consider Level 3 implementation)
  • Any 500 Internal Server Errors

Level 3 Considerations

When the current error handling becomes a bottleneck (>20% timeout rate), consider implementing:

  • Job Queues: Use BullMQ for asynchronous booking processing
  • 202 Accepted: Return immediate acceptance, process in background
  • Event Sourcing: Track booking attempts as events
  • Circuit Breaker: Prevent cascading failures during database outages

Related Files

  • src/lib/errors.ts - Core error definitions and utilities
  • src/lib/errorHandler.ts - Centralized error handling function
  • src/lib/db.ts - Database configuration with timeouts
  • src/services/*.ts - Service-layer error handling
  • src/routes/*.ts - Route-layer error handling

Clone this wiki locally