Error Handling
Error Handling
Section titled “Error Handling”Robust error handling is crucial for building reliable applications with OstrichDB. This guide covers all error types, handling strategies, and best practices for managing errors in the SDK.
Error Types
Section titled “Error Types”OstrichDBError
Section titled “OstrichDBError”The primary error type thrown by the SDK, providing detailed information about API failures:
import { OstrichDBError } from 'ostrichdb-js';
interface OstrichDBError extends Error { statusCode: number; // HTTP status code response: any; // Full response object message: string; // Error message name: 'OstrichDBError';}
Common Error Scenarios
Section titled “Common Error Scenarios”try { await db.create_project('my-project');} catch (error) { if (error instanceof OstrichDBError) { console.log('Status Code:', error.statusCode); console.log('Error Message:', error.message); console.log('Full Response:', error.response); }}
HTTP Status Codes
Section titled “HTTP Status Codes”Client Errors (4xx)
Section titled “Client Errors (4xx)”400 - Bad Request
Section titled “400 - Bad Request”Invalid request parameters or malformed data.
try { // Invalid data type await db.create_record('project', 'collection', 'cluster', 'record', 'INVALID_TYPE', 'value');} catch (error) { if (error instanceof OstrichDBError && error.statusCode === 400) { console.error('Invalid request parameters:', error.message); // Handle validation errors }}
401 - Unauthorized
Section titled “401 - Unauthorized”Missing or invalid authentication token.
try { await db.list_projects();} catch (error) { if (error instanceof OstrichDBError && error.statusCode === 401) { console.error('Authentication required'); // Redirect to login or refresh token redirectToLogin(); }}
403 - Forbidden
Section titled “403 - Forbidden”Valid authentication but insufficient permissions.
try { await db.delete_project('restricted-project');} catch (error) { if (error instanceof OstrichDBError && error.statusCode === 403) { console.error('Access denied:', error.message); // Show permission denied message showAccessDeniedDialog(); }}
404 - Not Found
Section titled “404 - Not Found”Requested resource doesn’t exist.
try { const record = await db.get_record('project', 'collection', 'cluster', 'nonexistent');} catch (error) { if (error instanceof OstrichDBError && error.statusCode === 404) { console.log('Record not found'); // Handle missing resource gracefully return null; }}
409 - Conflict
Section titled “409 - Conflict”Resource already exists when trying to create.
try { await db.create_project('existing-project');} catch (error) { if (error instanceof OstrichDBError && error.statusCode === 409) { console.log('Project already exists'); // Use existing project or suggest different name return handleExistingProject(); }}
429 - Too Many Requests
Section titled “429 - Too Many Requests”Rate limit exceeded.
try { await db.list_records('project', 'collection', 'cluster');} catch (error) { if (error instanceof OstrichDBError && error.statusCode === 429) { console.error('Rate limit exceeded'); // Implement exponential backoff await retryWithBackoff(() => db.list_records('project', 'collection', 'cluster')); }}
Server Errors (5xx)
Section titled “Server Errors (5xx)”500 - Internal Server Error
Section titled “500 - Internal Server Error”Server-side error occurred.
try { await db.create_collection('project', 'collection');} catch (error) { if (error instanceof OstrichDBError && error.statusCode === 500) { console.error('Server error:', error.message); // Log error and show user-friendly message logErrorToService(error); showServerErrorMessage(); }}
503 - Service Unavailable
Section titled “503 - Service Unavailable”Server temporarily unavailable.
try { await db.health_check();} catch (error) { if (error instanceof OstrichDBError && error.statusCode === 503) { console.error('Service temporarily unavailable'); // Implement retry logic await retryAfterDelay(() => db.health_check(), 5000); }}
Error Handling Strategies
Section titled “Error Handling Strategies”Basic Try-Catch
Section titled “Basic Try-Catch”async function basicErrorHandling() { try { const projects = await db.list_projects(); return projects; } catch (error) { if (error instanceof OstrichDBError) { console.error(`API Error (${error.statusCode}): ${error.message}`); } else { console.error('Unexpected error:', error); } throw error; // Re-throw if needed }}
Centralized Error Handler
Section titled “Centralized Error Handler”export class DatabaseErrorHandler { static handle(error: unknown, context?: string): void { const contextPrefix = context ? `[${context}] ` : '';
if (error instanceof OstrichDBError) { switch (error.statusCode) { case 400: console.error(`${contextPrefix}Bad Request: ${error.message}`); break; case 401: console.error(`${contextPrefix}Unauthorized - redirecting to login`); window.location.href = '/login'; break; case 403: console.error(`${contextPrefix}Forbidden: ${error.message}`); this.showAccessDeniedNotification(); break; case 404: console.warn(`${contextPrefix}Resource not found: ${error.message}`); break; case 409: console.warn(`${contextPrefix}Resource conflict: ${error.message}`); break; case 429: console.warn(`${contextPrefix}Rate limit exceeded`); this.handleRateLimit(); break; case 500: console.error(`${contextPrefix}Server error: ${error.message}`); this.logServerError(error); break; default: console.error(`${contextPrefix}API Error (${error.statusCode}): ${error.message}`); } } else if (error instanceof Error) { console.error(`${contextPrefix}Unexpected error: ${error.message}`); } else { console.error(`${contextPrefix}Unknown error:`, error); } }
private static showAccessDeniedNotification() { // Implementation depends on your UI framework console.log('Access denied - showing notification'); }
private static handleRateLimit() { // Implement rate limit handling console.log('Rate limit exceeded - implementing backoff'); }
private static logServerError(error: OstrichDBError) { // Log to external service console.log('Logging server error to monitoring service'); }}
// Usagetry { await db.create_project('test-project');} catch (error) { DatabaseErrorHandler.handle(error, 'Project Creation');}
Retry Logic with Exponential Backoff
Section titled “Retry Logic with Exponential Backoff”interface RetryOptions { maxRetries: number; baseDelay: number; maxDelay: number; retryCondition?: (error: any) => boolean;}
async function withRetry<T>( operation: () => Promise<T>, options: RetryOptions): Promise<T> { const { maxRetries, baseDelay, maxDelay, retryCondition } = options; let lastError: any;
for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error;
// Don't retry if we've reached max attempts if (attempt === maxRetries) { break; }
// Check if error is retryable if (retryCondition && !retryCondition(error)) { break; }
// Default retry condition for OstrichDB errors if (error instanceof OstrichDBError) { const isRetryable = error.statusCode >= 500 || error.statusCode === 429; if (!isRetryable) { break; } }
// Calculate delay with exponential backoff const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay); console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay)); } }
throw lastError;}
// Usage examplesasync function createProjectWithRetry(projectName: string) { return withRetry( () => db.create_project(projectName), { maxRetries: 3, baseDelay: 1000, maxDelay: 10000, retryCondition: (error) => { // Only retry on server errors and rate limits return error instanceof OstrichDBError && (error.statusCode >= 500 || error.statusCode === 429); } } );}
async function fetchRecordsWithRetry(project: string, collection: string, cluster: string) { return withRetry( () => db.list_records(project, collection, cluster), { maxRetries: 5, baseDelay: 500, maxDelay: 5000 } );}
Circuit Breaker Pattern
Section titled “Circuit Breaker Pattern”class CircuitBreaker { private failures = 0; private lastFailTime = 0; private state: 'closed' | 'open' | 'half-open' = 'closed';
constructor( private threshold: number = 5, private timeout: number = 60000, private resetTimeout: number = 30000 ) {}
async execute<T>(operation: () => Promise<T>): Promise<T> { if (this.state === 'open') { if (Date.now() - this.lastFailTime < this.resetTimeout) { throw new Error('Circuit breaker is open'); } this.state = 'half-open'; }
try { const result = await operation(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } }
private onSuccess() { this.failures = 0; this.state = 'closed'; }
private onFailure() { this.failures++; this.lastFailTime = Date.now();
if (this.failures >= this.threshold) { this.state = 'open'; } }
getState() { return this.state; }}
// Usageconst circuitBreaker = new CircuitBreaker(3, 60000, 30000);
async function robustDatabaseOperation() { try { return await circuitBreaker.execute(() => db.list_projects()); } catch (error) { if (error.message === 'Circuit breaker is open') { console.log('Service temporarily unavailable'); return getCachedProjects(); // Fallback to cache } throw error; }}
Framework-Specific Error Handling
Section titled “Framework-Specific Error Handling”Express.js Error Middleware
Section titled “Express.js Error Middleware”import { Request, Response, NextFunction } from 'express';import { OstrichDBError } from 'ostrichdb-js';
export function databaseErrorHandler( error: any, req: Request, res: Response, next: NextFunction) { if (error instanceof OstrichDBError) { const statusCode = error.statusCode; const message = getErrorMessage(statusCode, error.message);
console.error(`Database Error [${req.method} ${req.path}]:`, { statusCode, message: error.message, response: error.response });
res.status(statusCode).json({ error: message, statusCode, timestamp: new Date().toISOString(), path: req.path }); } else { next(error); }}
function getErrorMessage(statusCode: number, originalMessage: string): string { switch (statusCode) { case 400: return 'Invalid request parameters'; case 401: return 'Authentication required'; case 403: return 'Access denied'; case 404: return 'Resource not found'; case 409: return 'Resource already exists'; case 429: return 'Too many requests'; case 500: return 'Internal server error'; case 503: return 'Service temporarily unavailable'; default: return originalMessage; }}
// Usage in Express appapp.use('/api', apiRoutes);app.use(databaseErrorHandler);
Next.js Error Handling
Section titled “Next.js Error Handling”import { NextResponse } from 'next/server';import { OstrichDBError } from 'ostrichdb-js';
export function handleDatabaseError(error: unknown, context?: string) { console.error(`Database error${context ? ` (${context})` : ''}:`, error);
if (error instanceof OstrichDBError) { const statusCode = error.statusCode; const message = getClientSafeMessage(statusCode);
return NextResponse.json( { error: message, statusCode, timestamp: new Date().toISOString() }, { status: statusCode } ); }
// Generic error return NextResponse.json( { error: 'Internal server error', statusCode: 500, timestamp: new Date().toISOString() }, { status: 500 } );}
function getClientSafeMessage(statusCode: number): string { switch (statusCode) { case 400: return 'Invalid request data'; case 401: return 'Authentication required'; case 403: return 'Access denied'; case 404: return 'Resource not found'; case 409: return 'Resource already exists'; case 429: return 'Rate limit exceeded'; default: return 'Server error occurred'; }}
// Usage in API routeexport async function POST(request: NextRequest) { try { const db = new OstrichDB({ /* config */ }); await db.create_project('test');
return NextResponse.json({ success: true }); } catch (error) { return handleDatabaseError(error, 'Create Project'); }}
React Error Boundaries
Section titled “React Error Boundaries”import React, { Component, ReactNode } from 'react';import { OstrichDBError } from 'ostrichdb-js';
interface Props { children: ReactNode; fallback?: (error: Error) => ReactNode;}
interface State { hasError: boolean; error?: Error;}
export class DatabaseErrorBoundary extends Component<Props, State> { constructor(props: Props) { super(props); this.state = { hasError: false }; }
static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; }
componentDidCatch(error: Error, errorInfo: any) { console.error('Database error caught by boundary:', error, errorInfo);
if (error instanceof OstrichDBError) { // Log database errors to monitoring service this.logDatabaseError(error); } }
private logDatabaseError(error: OstrichDBError) { // Implementation depends on your logging service console.log('Logging database error:', { statusCode: error.statusCode, message: error.message, response: error.response }); }
render() { if (this.state.hasError && this.state.error) { if (this.props.fallback) { return this.props.fallback(this.state.error); }
if (this.state.error instanceof OstrichDBError) { return this.renderDatabaseError(this.state.error); }
return this.renderGenericError(); }
return this.props.children; }
private renderDatabaseError(error: OstrichDBError) { const message = this.getDatabaseErrorMessage(error.statusCode);
return ( <div className="error-boundary database-error"> <h2>Database Error</h2> <p>{message}</p> {error.statusCode === 401 && ( <button onClick={() => window.location.href = '/login'}> Go to Login </button> )} <button onClick={() => this.setState({ hasError: false })}> Try Again </button> </div> ); }
private getDatabaseErrorMessage(statusCode: number): string { switch (statusCode) { case 401: return 'Please log in to continue.'; case 403: return 'You don\'t have permission to access this resource.'; case 404: return 'The requested data was not found.'; case 429: return 'Too many requests. Please try again later.'; case 500: return 'A server error occurred. Please try again.'; default: return 'A database error occurred. Please try again.'; } }
private renderGenericError() { return ( <div className="error-boundary generic-error"> <h2>Something went wrong</h2> <p>An unexpected error occurred. Please try again.</p> <button onClick={() => this.setState({ hasError: false })}> Try Again </button> </div> ); }}
// Usagefunction App() { return ( <DatabaseErrorBoundary> <UserDashboard /> </DatabaseErrorBoundary> );}
Error Handling Utilities
Section titled “Error Handling Utilities”Error Classification
Section titled “Error Classification”import { OstrichDBError } from 'ostrichdb-js';
export class ErrorClassifier { static isRetryable(error: unknown): boolean { if (error instanceof OstrichDBError) { // Retry on server errors and rate limits return error.statusCode >= 500 || error.statusCode === 429; }
// Retry on network errors if (error instanceof Error) { return error.message.includes('network') || error.message.includes('timeout') || error.message.includes('ECONNRESET'); }
return false; }
static isAuthenticationError(error: unknown): boolean { return error instanceof OstrichDBError && error.statusCode === 401; }
static isAuthorizationError(error: unknown): boolean { return error instanceof OstrichDBError && error.statusCode === 403; }
static isResourceNotFound(error: unknown): boolean { return error instanceof OstrichDBError && error.statusCode === 404; }
static isResourceConflict(error: unknown): boolean { return error instanceof OstrichDBError && error.statusCode === 409; }
static isRateLimit(error: unknown): boolean { return error instanceof OstrichDBError && error.statusCode === 429; }
static isServerError(error: unknown): boolean { return error instanceof OstrichDBError && error.statusCode >= 500; }
static getErrorCategory(error: unknown): string { if (this.isAuthenticationError(error)) return 'authentication'; if (this.isAuthorizationError(error)) return 'authorization'; if (this.isResourceNotFound(error)) return 'not_found'; if (this.isResourceConflict(error)) return 'conflict'; if (this.isRateLimit(error)) return 'rate_limit'; if (this.isServerError(error)) return 'server_error'; if (this.isRetryable(error)) return 'retryable';
return 'unknown'; }}
Error Logging Service
Section titled “Error Logging Service”import { OstrichDBError } from 'ostrichdb-js';
interface ErrorLogEntry { timestamp: string; level: 'error' | 'warn' | 'info'; category: string; message: string; statusCode?: number; response?: any; context?: string; userId?: string;}
export class ErrorLogger { private logs: ErrorLogEntry[] = [];
log(error: unknown, context?: string, userId?: string) { const entry: ErrorLogEntry = { timestamp: new Date().toISOString(), level: this.getLogLevel(error), category: ErrorClassifier.getErrorCategory(error), message: this.getErrorMessage(error), context, userId };
if (error instanceof OstrichDBError) { entry.statusCode = error.statusCode; entry.response = error.response; }
this.logs.push(entry); this.writeLog(entry);
// Send to external service in production if (process.env.NODE_ENV === 'production') { this.sendToLoggingService(entry); } }
private getLogLevel(error: unknown): 'error' | 'warn' | 'info' { if (error instanceof OstrichDBError) { if (error.statusCode >= 500) return 'error'; if (error.statusCode === 404 || error.statusCode === 409) return 'info'; return 'warn'; } return 'error'; }
private getErrorMessage(error: unknown): string { if (error instanceof Error) { return error.message; } return String(error); }
private writeLog(entry: ErrorLogEntry) { const logMessage = `[${entry.timestamp}] ${entry.level.toUpperCase()}: ${entry.message}`;
switch (entry.level) { case 'error': console.error(logMessage, entry); break; case 'warn': console.warn(logMessage, entry); break; case 'info': console.info(logMessage, entry); break; } }
private async sendToLoggingService(entry: ErrorLogEntry) { // Implementation depends on your logging service // Example: Sentry, LogRocket, DataDog, etc. try { await fetch('/api/logs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(entry) }); } catch (logError) { console.error('Failed to send log to service:', logError); } }
getLogs(): ErrorLogEntry[] { return [...this.logs]; }
clearLogs() { this.logs = []; }}
// Global instanceexport const errorLogger = new ErrorLogger();
// Usagetry { await db.create_project('test');} catch (error) { errorLogger.log(error, 'Project Creation', 'user-123'); throw error;}
Best Practices
Section titled “Best Practices”1. Graceful Degradation
Section titled “1. Graceful Degradation”async function getUsersWithFallback(): Promise<User[]> { try { const users = await db.project('app').collection('users').cluster('active').listRecords(); return parseUsers(users); } catch (error) { if (ErrorClassifier.isServerError(error)) { console.warn('Database unavailable, using cached data'); return getCachedUsers(); }
if (ErrorClassifier.isResourceNotFound(error)) { console.info('No users found, returning empty array'); return []; }
throw error; // Re-throw unexpected errors }}
2. User-Friendly Messages
Section titled “2. User-Friendly Messages”function getUserFriendlyMessage(error: unknown): string { if (error instanceof OstrichDBError) { switch (error.statusCode) { 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 item was not found.'; case 409: return 'This item already exists. Please choose a different name.'; case 429: return 'Too many requests. Please wait a moment and try again.'; case 500: return 'A server error occurred. Please try again later.'; default: return 'An unexpected error occurred. Please try again.'; } }
return 'Something went wrong. Please try again.';}
3. Progressive Enhancement
Section titled “3. Progressive Enhancement”async function createProjectWithProgression(name: string): Promise<string> { try { // Try to create project await db.create_project(name); return 'Project created successfully'; } catch (error) { if (ErrorClassifier.isResourceConflict(error)) { // Project exists, try to use it try { const projects = await db.list_projects(); const existingProject = projects.find(p => p.name === name); if (existingProject) { return 'Using existing project'; } } catch (listError) { // Ignore list error, fall through to main error handling } }
// Handle other errors or re-throw throw error; }}
4. Monitoring and Alerting
Section titled “4. Monitoring and Alerting”class DatabaseMonitor { private errorCounts = new Map<number, number>(); private lastAlertTime = 0; private alertThreshold = 10; private alertCooldown = 300000; // 5 minutes
trackError(error: OstrichDBError) { const statusCode = error.statusCode; const count = this.errorCounts.get(statusCode) || 0; this.errorCounts.set(statusCode, count + 1);
// Check if we should send an alert if (count + 1 >= this.alertThreshold && Date.now() - this.lastAlertTime > this.alertCooldown) { this.sendAlert(statusCode, count + 1); this.lastAlertTime = Date.now(); } }
private async sendAlert(statusCode: number, count: number) { const message = `High error rate detected: ${count} errors with status ${statusCode}`; console.error('ALERT:', message);
// Send to monitoring service try { await fetch('/api/alerts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'database_error_spike', statusCode, count, timestamp: new Date().toISOString() }) }); } catch (alertError) { console.error('Failed to send alert:', alertError); } }
getErrorStats() { return Object.fromEntries(this.errorCounts); }
reset() { this.errorCounts.clear(); }}
// Global monitorexport const databaseMonitor = new DatabaseMonitor();
// Use in error handlingtry { await db.create_project('test');} catch (error) { if (error instanceof OstrichDBError) { databaseMonitor.trackError(error); } throw error;}
Testing Error Scenarios
Section titled “Testing Error Scenarios”Mock Error Responses
Section titled “Mock Error Responses”import { OstrichDBError } from 'ostrichdb-js';
describe('Error Handling', () => { test('should handle authentication error', async () => { const mockError = new OstrichDBError('Unauthorized', 401, {});
jest.spyOn(db, 'list_projects').mockRejectedValue(mockError);
try { await getUserProjects(); fail('Should have thrown error'); } catch (error) { expect(error).toBeInstanceOf(OstrichDBError); expect(error.statusCode).toBe(401); } });
test('should retry on server error', async () => { const serverError = new OstrichDBError('Internal Server Error', 500, {});
jest.spyOn(db, 'create_project') .mockRejectedValueOnce(serverError) .mockRejectedValueOnce(serverError) .mockResolvedValueOnce(undefined);
await expect(createProjectWithRetry('test')).resolves.toBeUndefined(); expect(db.create_project).toHaveBeenCalledTimes(3); });});
Next Steps
Section titled “Next Steps”Mastering error handling is crucial for robust applications:
- Configuration - Set up proper timeouts and retry policies
- Framework Integration - Apply error handling in your specific framework
- API Reference - Understand all possible error scenarios
- Builder Pattern - Use chainable operations with proper error handling
Comprehensive error handling ensures your application remains stable and provides excellent user experience even when things go wrong.