Error Handling

Learn how to handle errors effectively with the JustTCG JavaScript/TypeScript SDK.

Error Types

The SDK surfaces errors in two primary ways: thrown exceptions for critical SDK issues and an error property on the response object for API issues.

1. Thrown Exceptions (SDK-level)

Critical errors that happen at the SDK level will be thrown as JavaScript Error objects.

  • • Authentication failures (401)
  • • Invalid parameter values
  • • Network connectivity issues
  • • SDK configuration errors

2. Response Error Property (API-level)

API-level validation errors are returned in the response object.

  • • Invalid API keys (401/403)
  • • Rate limit exceeded (429)
  • • Missing required parameters
  • • Invalid lookup parameters

Handling Thrown Exceptions

SDK-level errors must be wrapped in a try...catch block:

try {
  const client = new JustTCG();
  const response = await client.v1.games.list();
  
  // Process successful response
  console.log('Games:', response.data);
} catch (error) {
  // Handle SDK-level errors
  if (error instanceof Error) {
    console.error('SDK Error:', error.message);
    
    // Handle specific error types
    if (error.message.includes('Authentication')) {
      console.error('Please check your API key');
    } else if (error.message.includes('Invalid parameter')) {
      console.error('Please check your request parameters');
    }
  }
}

Common SDK Errors

Authentication Error

Authentication error: API key is missing

Solution: Set the JUSTTCG_API_KEY environment variable or provide the key directly

Invalid Parameter Error

Invalid parameter value: orderBy must be one of 'price', '24h', '7d', '30d', '90d'

Solution: Check your parameter values against the API documentation

Handling Response Errors

API-level errors are returned in the response object and should be checked after each call:

const client = new JustTCG();
const response = await client.v1.sets.list();

// Check for API-level errors
if (response.error) {
  console.error('API Error:', response.error);
  console.error('Error Code:', response.code);

  // Handle specific error codes
  switch (response.code) {
    case 'INVALID_REQUEST':
      console.error('Invalid request parameters');
      break;
    case 'RATE_LIMIT_EXCEEDED':
      console.error('Rate limit exceeded, please wait');
      break;
    case 'UNAUTHORIZED':
      console.error('Invalid API key');
      break;
    default:
      console.error('Unknown API error');
  }

  return; // Exit early on error
}

// Process successful response
console.log('Sets:', response.data);

Common API Error Codes

INVALID_REQUESTMissing or invalid parameters
RATE_LIMIT_EXCEEDEDToo many requests
UNAUTHORIZEDInvalid API key
NOT_FOUNDResource not found

Comprehensive Error Handling

For production applications, you should implement comprehensive error handling that covers both error types:

import { JustTCG } from 'justtcg-js';

class RobustAPIClient {
  private client: JustTCG;

  constructor() {
    this.client = new JustTCG();
  }

  async safeApiCall<T>(apiCall: () => Promise<T>): Promise<{
    success: boolean;
    data?: T;
    error?: string;
    code?: string;
  }> {
    try {
      const result = await apiCall();
      
      // Check for API-level errors
      if (result && typeof result === 'object' && 'error' in result && result.error) {
        return {
          success: false,
          error: result.error,
          code: result.code
        };
      }

      return {
        success: true,
        data: result
      };
    } catch (error) {
      // Handle SDK-level errors
      if (error instanceof Error) {
        return {
          success: false,
          error: error.message
        };
      }

      return {
        success: false,
        error: 'Unknown error occurred'
      };
    }
  }

  async getGames() {
    const result = await this.safeApiCall(() => this.client.v1.games.list());
    
    if (result.success) {
      console.log('Games:', result.data?.data);
      return result.data;
    } else {
      console.error('Failed to get games:', result.error);
      return null;
    }
  }

  async getCard(cardId: string) {
    const result = await this.safeApiCall(() => 
      this.client.v1.cards.get({ cardId })
    );
    
    if (result.success) {
      return result.data;
    } else {
      console.error(`Failed to get card ${cardId}:`, result.error);
      return null;
    }
  }
}

// Usage
const apiClient = new RobustAPIClient();
const games = await apiClient.getGames();
const card = await apiClient.getCard('some-card-id');

Retry Logic and Resilience

For production applications, implement retry logic to handle temporary failures:

class ResilientAPIClient {
  private client: JustTCG;
  private maxRetries: number = 3;
  private retryDelay: number = 1000;

  constructor() {
    this.client = new JustTCG();
  }

  async withRetry<T>(apiCall: () => Promise<T>): Promise<T | null> {
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        const result = await apiCall();
        
        // Check for API-level errors
        if (result && typeof result === 'object' && 'error' in result && result.error) {
          // Don't retry on API errors (they won't change)
          if (result.code === 'RATE_LIMIT_EXCEEDED') {
            // Wait longer for rate limit errors
            await this.delay(this.retryDelay * 2);
            continue;
          }
          
          throw new Error(result.error);
        }

        return result;
      } catch (error) {
        console.error(`Attempt ${attempt} failed:`, error);

        if (attempt === this.maxRetries) {
          console.error('All retry attempts failed');
          return null;
        }

        // Wait before retrying (exponential backoff)
        await this.delay(this.retryDelay * attempt);
      }
    }
    return null;
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  async getGames() {
    return this.withRetry(() => this.client.v1.games.list());
  }
}

// Usage
const resilientClient = new ResilientAPIClient();
const games = await resilientClient.getGames();

Error Logging and Monitoring

Implement proper error logging and monitoring for production applications:

interface ErrorLog {
  timestamp: Date;
  error: string;
  code?: string;
  context: string;
  severity: 'low' | 'medium' | 'high' | 'critical';
}

class MonitoredAPIClient {
  private client: JustTCG;
  private errorLogs: ErrorLog[] = [];

  constructor() {
    this.client = new JustTCG();
  }

  private logError(error: string, code?: string, context: string = 'API Call', severity: ErrorLog['severity'] = 'medium') {
    const logEntry: ErrorLog = {
      timestamp: new Date(),
      error,
      code,
      context,
      severity
    };

    this.errorLogs.push(logEntry);
    
    // Log to console in development
    if (process.env.NODE_ENV === 'development') {
      console.error(`[${severity.toUpperCase()}] ${context}:`, error);
    }

    // In production, send to your logging service
    if (process.env.NODE_ENV === 'production') {
      this.sendToLoggingService(logEntry);
    }
  }

  private sendToLoggingService(logEntry: ErrorLog) {
    // Send to your preferred logging service (e.g., Sentry, LogRocket, etc.)
    console.log('Sending to logging service:', logEntry);
  }

  async getGames() {
    try {
      const response = await this.client.v1.games.list();
      
      if (response.error) {
        this.logError(response.error, response.code, 'Get Games', 'medium');
        return null;
      }

      return response;
    } catch (error) {
      this.logError(
        error instanceof Error ? error.message : 'Unknown error',
        undefined,
        'Get Games',
        'high'
      );
      return null;
    }
  }

  getErrorLogs(): ErrorLog[] {
    return this.errorLogs;
  }

  getErrorStats() {
    const total = this.errorLogs.length;
    const bySeverity = this.errorLogs.reduce((acc, log) => {
      acc[log.severity] = (acc[log.severity] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);

    return { total, bySeverity };
  }
}

// Usage
const monitoredClient = new MonitoredAPIClient();
const games = await monitoredClient.getGames();

// Check error statistics
const stats = monitoredClient.getErrorStats();
console.log('Error statistics:', stats);

Best Practices

1. Always Handle Both Error Types

Use try-catch for SDK errors and check response.error for API errors.

2. Implement Retry Logic

Retry transient errors with exponential backoff, but don't retry API validation errors.

3. Log Errors Appropriately

Log errors with sufficient context for debugging, but avoid logging sensitive information.

4. Monitor API Usage

Check usage metadata to avoid hitting rate limits and plan limits.

5. Provide User-Friendly Messages

Transform technical error messages into user-friendly notifications when appropriate.

Next Steps