---
title: "Error Handling"
description: "Comprehensive error handling with the TraceRTM JavaScript SDK"
---

# Error Handling

The TraceRTM SDK provides typed errors for precise error handling.

## Error Hierarchy

All SDK errors extend the base `TraceRTMError` class:

```typescript
import {
  TraceRTMError,
  NotFoundError,
  ValidationError,
  AuthenticationError,
  ForbiddenError,
  RateLimitError,
  ServerError,
  NetworkError,
} from "@tracertm/sdk/errors";
```

### Error Class Hierarchy

```
TraceRTMError (base)
├── NotFoundError (404)
├── ValidationError (400)
├── AuthenticationError (401)
├── ForbiddenError (403)
├── RateLimitError (429)
├── ServerError (500+)
└── NetworkError (connection issues)
```

---

## Error Types

### TraceRTMError

Base class for all SDK errors:

```typescript
class TraceRTMError extends Error {
  name: string;
  code: string;
  statusCode?: number;
  requestId?: string;
  details?: Record<string, unknown>;
}
```

### NotFoundError

Thrown when a resource doesn't exist (HTTP 404):

```typescript
try {
  await client.items.get("nonexistent_id");
} catch (error) {
  if (error instanceof NotFoundError) {
    console.log("Resource not found");
    console.log(`Code: ${error.code}`); // "NOT_FOUND"
    console.log(`Status: ${error.statusCode}`); // 404
  }
}
```

### ValidationError

Thrown for invalid input (HTTP 400):

```typescript
class ValidationError extends TraceRTMError {
  code: "VALIDATION_ERROR";
  statusCode: 400;
  details: ValidationErrorDetail[];
}

interface ValidationErrorDetail {
  field: string;
  message: string;
  code: string;
}
```

```typescript
try {
  await client.items.create({
    projectId: "proj_123",
    type: "invalid_type" as any,
    title: "", // Empty title
  });
} catch (error) {
  if (error instanceof ValidationError) {
    console.log("Validation failed:");
    for (const detail of error.details) {
      console.log(`  ${detail.field}: ${detail.message}`);
    }
    // Output:
    // Validation failed:
    //   type: Invalid item type
    //   title: Title is required
  }
}
```

### AuthenticationError

Thrown for invalid credentials (HTTP 401):

```typescript
try {
  const client = new TraceRTMClient({ apiKey: "invalid_key" });
  await client.projects.list();
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.log("Invalid API key");
    // Prompt user to re-authenticate
  }
}
```

### ForbiddenError

Thrown when access is denied (HTTP 403):

```typescript
try {
  await client.projects.delete("proj_someone_elses");
} catch (error) {
  if (error instanceof ForbiddenError) {
    console.log("You don't have permission to perform this action");
  }
}
```

### RateLimitError

Thrown when rate limits are exceeded (HTTP 429):

```typescript
class RateLimitError extends TraceRTMError {
  code: "RATE_LIMITED";
  statusCode: 429;
  retryAfter: number; // Seconds until limit resets
  limit: number;      // Request limit
  remaining: number;  // Remaining requests
}
```

```typescript
try {
  await client.items.list({ projectId: "proj_123" });
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
    console.log(`Limit: ${error.limit}, Remaining: ${error.remaining}`);

    // Wait and retry
    await new Promise((r) => setTimeout(r, error.retryAfter * 1000));
    return client.items.list({ projectId: "proj_123" });
  }
}
```

### ServerError

Thrown for server-side errors (HTTP 500+):

```typescript
try {
  await client.items.get("item_123");
} catch (error) {
  if (error instanceof ServerError) {
    console.log("Server error:", error.message);
    console.log("Request ID:", error.requestId);
    // Log for support ticket
  }
}
```

### NetworkError

Thrown for network connectivity issues:

```typescript
try {
  await client.items.get("item_123");
} catch (error) {
  if (error instanceof NetworkError) {
    console.log("Network error:", error.message);
    // Check connectivity, retry later
  }
}
```

---

## Basic Error Handling

### Using try/catch

```typescript
async function getItem(id: string) {
  try {
    return await client.items.get(id);
  } catch (error) {
    if (error instanceof NotFoundError) {
      return null;
    }
    throw error;
  }
}
```

### Type-Discriminated Handling

```typescript
async function handleRequest() {
  try {
    return await client.items.create({
      projectId: "proj_123",
      type: "requirement",
      title: "New Requirement",
    });
  } catch (error) {
    if (!(error instanceof TraceRTMError)) {
      throw error; // Unknown error
    }

    switch (error.code) {
      case "NOT_FOUND":
        console.log("Project not found");
        break;
      case "VALIDATION_ERROR":
        console.log("Invalid input:", (error as ValidationError).details);
        break;
      case "AUTHENTICATION_ERROR":
        console.log("Please log in again");
        break;
      case "FORBIDDEN":
        console.log("Access denied");
        break;
      case "RATE_LIMITED":
        console.log(`Wait ${(error as RateLimitError).retryAfter}s`);
        break;
      case "SERVER_ERROR":
        console.log("Server error, try again later");
        break;
      default:
        throw error;
    }
  }
}
```

---

## Advanced Patterns

### Error Recovery

```typescript
async function getItemWithFallback(id: string, fallback: Item): Promise<Item> {
  try {
    return await client.items.get(id);
  } catch (error) {
    if (error instanceof NotFoundError) {
      return fallback;
    }
    throw error;
  }
}
```

### Retry with Backoff

```typescript
async function withRetry<T>(
  fn: () => Promise<T>,
  options: {
    maxRetries?: number;
    baseDelay?: number;
    retryOn?: Array<new (...args: any[]) => TraceRTMError>;
  } = {}
): Promise<T> {
  const {
    maxRetries = 3,
    baseDelay = 1000,
    retryOn = [RateLimitError, ServerError, NetworkError],
  } = options;

  let lastError: Error;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;

      // Check if error is retryable
      const isRetryable = retryOn.some((ErrorClass) =>
        error instanceof ErrorClass
      );

      if (!isRetryable || attempt === maxRetries) {
        throw error;
      }

      // Calculate delay
      let delay = baseDelay * Math.pow(2, attempt);

      // Use Retry-After header for rate limits
      if (error instanceof RateLimitError) {
        delay = error.retryAfter * 1000;
      }

      console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
      await new Promise((r) => setTimeout(r, delay));
    }
  }

  throw lastError!;
}

// Usage
const item = await withRetry(() => client.items.get("item_123"));
```

### Global Error Handler

```typescript
class ApiClient {
  private client: TraceRTMClient;
  private onError?: (error: TraceRTMError) => void;

  constructor(apiKey: string, onError?: (error: TraceRTMError) => void) {
    this.client = new TraceRTMClient({ apiKey });
    this.onError = onError;
  }

  async request<T>(fn: () => Promise<T>): Promise<T> {
    try {
      return await fn();
    } catch (error) {
      if (error instanceof TraceRTMError && this.onError) {
        this.onError(error);
      }
      throw error;
    }
  }

  async getItem(id: string) {
    return this.request(() => this.client.items.get(id));
  }
}

// Usage
const api = new ApiClient("key", (error) => {
  // Log to monitoring service
  analytics.trackError({
    code: error.code,
    message: error.message,
    requestId: error.requestId,
  });

  // Show user-friendly message
  if (error instanceof AuthenticationError) {
    showToast("Session expired. Please log in again.");
  }
});
```

---

## Error Formatting

### User-Friendly Messages

```typescript
function getErrorMessage(error: unknown): string {
  if (error instanceof NotFoundError) {
    return "The requested item could not be found.";
  }

  if (error instanceof ValidationError) {
    const fields = error.details.map((d) => d.field).join(", ");
    return `Please check the following fields: ${fields}`;
  }

  if (error instanceof AuthenticationError) {
    return "Your session has expired. Please log in again.";
  }

  if (error instanceof ForbiddenError) {
    return "You don't have permission to perform this action.";
  }

  if (error instanceof RateLimitError) {
    return `Too many requests. Please wait ${error.retryAfter} seconds.`;
  }

  if (error instanceof ServerError) {
    return "Something went wrong on our end. Please try again later.";
  }

  if (error instanceof NetworkError) {
    return "Unable to connect. Please check your internet connection.";
  }

  return "An unexpected error occurred.";
}
```

### Detailed Error Logging

```typescript
function logError(error: unknown, context: Record<string, unknown> = {}) {
  if (!(error instanceof TraceRTMError)) {
    console.error("Unknown error:", error);
    return;
  }

  const logData = {
    timestamp: new Date().toISOString(),
    code: error.code,
    message: error.message,
    statusCode: error.statusCode,
    requestId: error.requestId,
    details: error.details,
    ...context,
  };

  if (error instanceof ValidationError) {
    logData.validationErrors = error.details;
  }

  if (error instanceof RateLimitError) {
    logData.retryAfter = error.retryAfter;
    logData.rateLimit = error.limit;
  }

  console.error("API Error:", JSON.stringify(logData, null, 2));
}
```

---

## Framework Integration

### Express Middleware

```typescript
import { Request, Response, NextFunction } from "express";

function traceRTMErrorHandler(
  error: Error,
  req: Request,
  res: Response,
  next: NextFunction
) {
  if (!(error instanceof TraceRTMError)) {
    return next(error);
  }

  const response: Record<string, unknown> = {
    error: {
      code: error.code,
      message: getErrorMessage(error),
    },
  };

  if (error instanceof ValidationError) {
    response.error.details = error.details;
  }

  res.status(error.statusCode || 500).json(response);
}

// Usage
app.use(traceRTMErrorHandler);
```

### React Error Boundary

```tsx
import React from "react";
import { TraceRTMError, AuthenticationError } from "@tracertm/sdk/errors";

interface State {
  error: Error | null;
}

class TraceRTMErrorBoundary extends React.Component<
  { children: React.ReactNode },
  State
> {
  state: State = { error: null };

  static getDerivedStateFromError(error: Error) {
    return { error };
  }

  componentDidCatch(error: Error) {
    if (error instanceof AuthenticationError) {
      // Redirect to login
      window.location.href = "/login";
      return;
    }

    // Log error
    console.error("TraceRTM Error:", error);
  }

  render() {
    if (this.state.error) {
      if (this.state.error instanceof TraceRTMError) {
        return (
          <div className="error">
            <h2>Something went wrong</h2>
            <p>{getErrorMessage(this.state.error)}</p>
            <button onClick={() => this.setState({ error: null })}>
              Try Again
            </button>
          </div>
        );
      }
    }

    return this.props.children;
  }
}
```

### React Query Integration

```typescript
import { useQuery, useMutation } from "@tanstack/react-query";

function useItem(id: string) {
  return useQuery({
    queryKey: ["item", id],
    queryFn: () => client.items.get(id),
    retry: (failureCount, error) => {
      // Don't retry for client errors
      if (
        error instanceof NotFoundError ||
        error instanceof ValidationError ||
        error instanceof ForbiddenError
      ) {
        return false;
      }

      // Retry up to 3 times for server/network errors
      return failureCount < 3;
    },
    retryDelay: (attemptIndex, error) => {
      if (error instanceof RateLimitError) {
        return error.retryAfter * 1000;
      }
      return Math.min(1000 * 2 ** attemptIndex, 30000);
    },
  });
}
```

---

## Error Utilities

### Type Guards

```typescript
function isTraceRTMError(error: unknown): error is TraceRTMError {
  return error instanceof TraceRTMError;
}

function isNotFoundError(error: unknown): error is NotFoundError {
  return error instanceof NotFoundError;
}

function isValidationError(error: unknown): error is ValidationError {
  return error instanceof ValidationError;
}

function isRetryableError(error: unknown): boolean {
  return (
    error instanceof RateLimitError ||
    error instanceof ServerError ||
    error instanceof NetworkError
  );
}
```

### Result Type Pattern

```typescript
type Result<T, E = TraceRTMError> =
  | { success: true; data: T }
  | { success: false; error: E };

async function safeRequest<T>(fn: () => Promise<T>): Promise<Result<T>> {
  try {
    const data = await fn();
    return { success: true, data };
  } catch (error) {
    if (error instanceof TraceRTMError) {
      return { success: false, error };
    }
    throw error;
  }
}

// Usage
const result = await safeRequest(() => client.items.get("item_123"));

if (result.success) {
  console.log("Item:", result.data.title);
} else {
  console.log("Error:", result.error.message);
}
```

---

## Best Practices

### 1. Be Specific

```typescript
// Good - handle specific errors
try {
  await client.items.get(id);
} catch (error) {
  if (error instanceof NotFoundError) {
    return null;
  }
  throw error; // Re-throw unexpected errors
}

// Bad - catch all errors
try {
  await client.items.get(id);
} catch (error) {
  return null; // Hides real errors
}
```

### 2. Preserve Context

```typescript
async function createItem(data: CreateItemInput) {
  try {
    return await client.items.create(data);
  } catch (error) {
    if (error instanceof ValidationError) {
      // Add context before re-throwing
      error.message = `Failed to create item: ${error.message}`;
      throw error;
    }
    throw error;
  }
}
```

### 3. Handle at Appropriate Level

```typescript
// Low-level: Just propagate
async function fetchItem(id: string) {
  return client.items.get(id);
}

// Mid-level: Transform to domain errors
async function getRequirement(id: string) {
  try {
    const item = await fetchItem(id);
    if (item.type !== "requirement") {
      throw new Error("Not a requirement");
    }
    return item;
  } catch (error) {
    if (error instanceof NotFoundError) {
      throw new RequirementNotFoundError(id);
    }
    throw error;
  }
}

// High-level: Handle and display
async function loadRequirement(id: string) {
  try {
    return await getRequirement(id);
  } catch (error) {
    showToast(getErrorMessage(error));
    return null;
  }
}
```

---

## Related Documentation

- [Async Operations](/docs/api-reference/sdks/javascript/async) - Async patterns
- [Testing](/docs/api-reference/sdks/javascript/testing) - Testing errors
- [Logging](/docs/api-reference/sdks/javascript/logging) - Error logging
