Error Handling
Proper error handling is crucial when working with the Panels API client. This guide covers common error scenarios, best practices, and recovery strategies.
Overview
The Panels API client provides TypeScript-safe error handling with proper HTTP status codes and meaningful error messages. All API methods return promises that can be caught using try-catch blocks.
Basic Error Handling
Simple Try-Catch
typescript
import { panelsAPI } from '@panels/app/api'
async function createPanelSafely() {
try {
const panel = await panelsAPI.create({
name: "My Panel",
tenantId: "tenant-123",
userId: "user-456"
})
console.log("Panel created:", panel.id)
return panel
} catch (error) {
console.error("Failed to create panel:", error)
throw error
}
}
Comprehensive Error Handler
typescript
async function handlePanelCreation() {
try {
const panel = await panelsAPI.create({
name: "My Panel",
tenantId: "tenant-123",
userId: "user-456"
})
return { success: true, data: panel }
} catch (error) {
return {
success: false,
error: error.message || "Unknown error occurred"
}
}
}
// Usage
const result = await handlePanelCreation()
if (result.success) {
console.log("Panel created:", result.data.id)
} else {
console.error("Error:", result.error)
}
Common Error Types
Network Errors
typescript
async function handleNetworkErrors() {
try {
const panels = await panelsAPI.all("tenant-123", "user-456")
return panels
} catch (error) {
if (error.name === 'NetworkError' || error.code === 'ECONNREFUSED') {
console.error("Network connection failed. Check if the server is running.")
throw new Error("Unable to connect to the server. Please try again later.")
}
throw error
}
}
Authentication Errors
typescript
async function handleAuthErrors() {
try {
const view = await viewsAPI.create({
name: "My View",
panelId: 123,
config: { columns: ["email"] },
tenantId: "tenant-123",
userId: "user-456"
})
return view
} catch (error) {
if (error.status === 401) {
console.error("Authentication failed. Please check your credentials.")
// Redirect to login or refresh token
window.location.href = '/login'
return
}
if (error.status === 403) {
console.error("Access forbidden. You don't have permission for this action.")
throw new Error("Insufficient permissions")
}
throw error
}
}
Validation Errors
typescript
async function handleValidationErrors() {
try {
const column = await panelsAPI.columns.createBase("123", {
name: "", // Invalid: empty name
type: "text",
sourceField: "email",
dataSourceId: 456,
properties: {},
tenantId: "tenant-123",
userId: "user-456"
})
return column
} catch (error) {
if (error.status === 400) {
console.error("Validation error:", error.message)
// Extract specific validation errors
if (error.details) {
error.details.forEach(detail => {
console.error(`- ${detail.field}: ${detail.message}`)
})
}
throw new Error("Please check your input and try again")
}
throw error
}
}
Resource Not Found
typescript
async function handleNotFoundErrors() {
try {
const panel = await panelsAPI.get({ id: 999999 })
return panel
} catch (error) {
if (error.status === 404) {
console.error("Panel not found")
return null // Or handle gracefully
}
throw error
}
}
Error Recovery Strategies
Retry with Exponential Backoff
typescript
async function retryWithBackoff<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
baseDelay: number = 1000
): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation()
} catch (error) {
if (attempt === maxRetries) {
throw error
}
// Only retry on network errors or server errors
if (error.status >= 500 || error.name === 'NetworkError') {
const delay = baseDelay * Math.pow(2, attempt - 1)
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`)
await new Promise(resolve => setTimeout(resolve, delay))
} else {
throw error // Don't retry client errors
}
}
}
}
// Usage
const panels = await retryWithBackoff(() =>
panelsAPI.all("tenant-123", "user-456")
)
Graceful Degradation
typescript
async function loadPanelWithFallback(panelId: number) {
try {
// Try to load full panel data
const panel = await panelsAPI.get({ id: panelId })
const columns = await panelsAPI.columns.list(
panelId.toString(),
"tenant-123",
"user-456"
)
return { panel, columns, status: 'complete' }
} catch (error) {
if (error.status === 404) {
return { panel: null, columns: null, status: 'not_found' }
}
try {
// Fallback: try to load just basic panel info
const panel = await panelsAPI.get({ id: panelId })
return { panel, columns: null, status: 'partial' }
} catch (fallbackError) {
return { panel: null, columns: null, status: 'error' }
}
}
}
Error Boundaries for React
typescript
import { Component, ReactNode } from 'react'
interface ErrorBoundaryState {
hasError: boolean
error: Error | null
}
class PanelsErrorBoundary extends Component<
{ children: ReactNode },
ErrorBoundaryState
> {
constructor(props: { children: ReactNode }) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error }
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong with the Panels API</h2>
<details>
<summary>Error Details</summary>
<pre>{this.state.error?.message}</pre>
</details>
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
)
}
return this.props.children
}
}
// Usage
function App() {
return (
<PanelsErrorBoundary>
<PanelsDashboard />
</PanelsErrorBoundary>
)
}
Custom Error Classes
typescript
class PanelsAPIError extends Error {
constructor(
message: string,
public status: number,
public details?: any
) {
super(message)
this.name = 'PanelsAPIError'
}
}
class ValidationError extends PanelsAPIError {
constructor(message: string, public validationErrors: any[]) {
super(message, 400, validationErrors)
this.name = 'ValidationError'
}
}
class NotFoundError extends PanelsAPIError {
constructor(resource: string, id: string | number) {
super(`${resource} with ID ${id} not found`, 404)
this.name = 'NotFoundError'
}
}
// Usage in API wrapper
async function safeAPICall<T>(apiCall: () => Promise<T>): Promise<T> {
try {
return await apiCall()
} catch (error) {
if (error.status === 400) {
throw new ValidationError(error.message, error.details)
}
if (error.status === 404) {
throw new NotFoundError("Resource", "unknown")
}
throw new PanelsAPIError(error.message, error.status, error.details)
}
}
Logging and Monitoring
typescript
interface ErrorLogger {
logError(error: Error, context: any): void
}
class ConsoleErrorLogger implements ErrorLogger {
logError(error: Error, context: any): void {
console.error('Panels API Error:', {
message: error.message,
name: error.name,
context,
timestamp: new Date().toISOString()
})
}
}
class APIErrorHandler {
constructor(private logger: ErrorLogger) {}
async handleOperation<T>(
operation: () => Promise<T>,
context: any
): Promise<T> {
try {
return await operation()
} catch (error) {
this.logger.logError(error, context)
throw error
}
}
}
// Usage
const errorHandler = new APIErrorHandler(new ConsoleErrorLogger())
const panel = await errorHandler.handleOperation(
() => panelsAPI.create({
name: "My Panel",
tenantId: "tenant-123",
userId: "user-456"
}),
{ operation: 'createPanel', userId: 'user-456' }
)
Best Practices
1. Always Handle Errors
typescript
// ❌ Bad: No error handling
const panel = await panelsAPI.create(panelData)
// ✅ Good: Proper error handling
try {
const panel = await panelsAPI.create(panelData)
// Handle success
} catch (error) {
// Handle error
}
2. Provide User-Friendly Messages
typescript
function getErrorMessage(error: any): string {
switch (error.status) {
case 400:
return "Please check your input and try again."
case 401:
return "Please log in to continue."
case 403:
return "You don't have permission to perform this action."
case 404:
return "The requested resource was not found."
case 500:
return "A server error occurred. Please try again later."
default:
return "An unexpected error occurred. Please try again."
}
}
3. Log Context Information
typescript
try {
await panelsAPI.create(panelData)
} catch (error) {
console.error('Panel creation failed:', {
error: error.message,
panelData,
userId: currentUser.id,
timestamp: new Date().toISOString()
})
}
4. Don't Swallow Errors
typescript
// ❌ Bad: Silently ignoring errors
try {
await panelsAPI.create(panelData)
} catch (error) {
// Do nothing
}
// ✅ Good: Handle or re-throw
try {
await panelsAPI.create(panelData)
} catch (error) {
console.error("Failed to create panel:", error)
throw new Error("Panel creation failed")
}
Testing Error Handling
typescript
import { describe, it, expect, vi } from 'vitest'
describe('Panel API Error Handling', () => {
it('should handle network errors gracefully', async () => {
// Mock network error
vi.spyOn(global, 'fetch').mockRejectedValue(
new Error('Network Error')
)
const result = await handlePanelCreation()
expect(result.success).toBe(false)
expect(result.error).toContain('Network')
})
it('should handle validation errors', async () => {
// Mock validation error response
vi.spyOn(global, 'fetch').mockResolvedValue({
ok: false,
status: 400,
json: () => Promise.resolve({
message: 'Validation failed',
details: [{ field: 'name', message: 'Name is required' }]
})
})
await expect(panelsAPI.create(invalidData)).rejects.toThrow('Validation failed')
})
})