# Error Handling Guide This document describes the error handling patterns implemented in the VIP Coordinator application. ## Backend Error Handling ### Global Exception Filters The backend uses two global exception filters for consistent error responses: #### 1. HttpExceptionFilter Handles all HTTP exceptions (4xx, 5xx errors) and formats them consistently. **Features:** - Standardized error response format - Automatic logging with appropriate levels (error/warn/log) - Request context logging (params, query, body) - Sensitive data redaction (passwords, tokens, API keys) **Error Response Format:** ```json { "statusCode": 400, "timestamp": "2026-01-25T10:30:00.000Z", "path": "/api/v1/events", "method": "POST", "message": "Validation failed", "error": "Bad Request", "details": { ... } } ``` #### 2. AllExceptionsFilter Catch-all filter for unhandled errors (database errors, runtime exceptions, etc.). **Features:** - Catches all non-HTTP exceptions - Prevents server crashes from unhandled errors - Logs full stack traces - Returns 500 Internal Server Error to client - In development: includes stack trace in response ### Using Exceptions in Controllers/Services **Example:** ```typescript import { BadRequestException, NotFoundException } from '@nestjs/common'; // Simple error throw new NotFoundException('VIP not found'); // Error with details throw new BadRequestException({ message: 'Driver has conflicting events', conflicts: [ { id: '123', title: 'Airport Pickup', startTime: '...' } ] }); ``` ### Logging Best Practices ```typescript import { Logger } from '@nestjs/common'; export class MyService { private readonly logger = new Logger(MyService.name); async doSomething() { this.logger.log('Starting operation...'); this.logger.warn('Conflict detected'); this.logger.error('Operation failed', error.stack); } } ``` ## Frontend Error Handling ### ErrorHandler Utility The `ErrorHandler` class provides centralized error handling for the frontend. **Location:** `frontend/src/lib/errorHandler.ts` ### Quick Usage ```typescript import { handleError } from '@/lib/errorHandler'; try { await api.createVIP(data); toast.success('VIP created successfully'); } catch (error) { handleError('VIP Creation', error, 'Failed to create VIP'); } ``` ### Advanced Usage #### Extract Error Message Only ```typescript import { ErrorHandler } from '@/lib/errorHandler'; try { await api.get('/vips'); } catch (error) { const message = ErrorHandler.getMessage(error, 'Failed to load VIPs'); setErrorMessage(message); } ``` #### Show Toast Only ```typescript ErrorHandler.showError(error, 'Operation failed'); ``` #### Log Only (No Toast) ```typescript ErrorHandler.log('VIP Fetch', error, { vipId: '123' }); ``` #### Check Error Type ```typescript try { await api.updateVIP(id, data); } catch (error) { if (ErrorHandler.isAuthError(error)) { // Redirect to login navigate('/login'); } else if (ErrorHandler.isConflict(error)) { // Show conflict resolution UI const conflicts = ErrorHandler.getConflicts(error); setConflicts(conflicts); } else { ErrorHandler.showError(error); } } ``` ### Available Methods | Method | Description | Example | |--------|-------------|---------| | `getMessage(error, fallback?)` | Extract user-friendly message | `const msg = ErrorHandler.getMessage(error)` | | `showError(error, fallback?)` | Show error toast | `ErrorHandler.showError(error)` | | `log(context, error, info?)` | Log to console with context | `ErrorHandler.log('API', error, { id })` | | `handle(context, error, fallback?, info?)` | Log + toast | `ErrorHandler.handle('Save', error)` | | `isAuthError(error)` | Check if 401/403 | `if (ErrorHandler.isAuthError(error))` | | `isConflict(error)` | Check if 409 | `if (ErrorHandler.isConflict(error))` | | `isValidationError(error)` | Check if 400 | `if (ErrorHandler.isValidationError(error))` | | `getConflicts(error)` | Extract conflicts array | `const conflicts = ErrorHandler.getConflicts(error)` | ### API Client Logging The axios client automatically logs all requests and responses in development mode. **Console output format:** ``` [API] → POST /api/v1/vips { data: {...} } [API] ← 201 POST /api/v1/vips { data: {...} } [API] ✖ 400 POST /api/v1/events { status: 400, data: {...} } ``` **Error types logged:** - 401: Authentication required - 403: Permission denied - 404: Resource not found - 409: Conflict (with conflict details) - 500+: Server error ### Migration Guide #### Before (Old Pattern) ```typescript try { await api.createVIP(data); toast.success('VIP created'); } catch (error: any) { console.error('Failed to create VIP:', error); toast.error(error.response?.data?.message || 'Failed to create VIP'); } ``` #### After (New Pattern) ```typescript import { handleError } from '@/lib/errorHandler'; try { await api.createVIP(data); toast.success('VIP created'); } catch (error) { handleError('VIP Creation', error, 'Failed to create VIP'); } ``` **Benefits:** - ✅ Consistent error messages across app - ✅ Automatic console logging with context - ✅ Handles all error types (Axios, Error, unknown) - ✅ Type-safe error checking - ✅ Less code duplication ## Common Error Scenarios ### 1. Network Error (Backend Down) **Backend logs:** None (backend not reachable) **Frontend logs:** `[API] ✖ Network error - no response received` **User sees:** Toast: "Network error. Please check your connection." ### 2. Authentication Error (401) **Backend logs:** `[WARN] [GET] /api/v1/vips - 401 - Unauthorized` **Frontend logs:** `[API] ✖ 401 GET /api/v1/vips` + warning **User sees:** Toast: "Authentication required. Please log in again." ### 3. Permission Error (403) **Backend logs:** `[WARN] [POST] /api/v1/users - 403 - Forbidden resource` **Frontend logs:** `[API] ✖ 403 POST /api/v1/users` + warning **User sees:** Toast: "You do not have permission to perform this action." ### 4. Validation Error (400) **Backend logs:** `[WARN] [POST] /api/v1/events - 400 - Validation failed` **Frontend logs:** `[API] ✖ 400 POST /api/v1/events` + response data **User sees:** Toast: "Validation failed: email must be a valid email" ### 5. Conflict Error (409) **Backend logs:** `[WARN] [POST] /api/v1/events - 409 - Driver has conflicting events` **Frontend logs:** `[API] ✖ 409 POST /api/v1/events` + conflicts array **User sees:** Toast: "A conflict occurred. Please check for overlapping schedules." ### 6. Server Error (500) **Backend logs:** `[ERROR] [GET] /api/v1/vips - 500 - Internal server error` + stack trace **Frontend logs:** `[API] ✖ 500 GET /api/v1/vips` + error message **User sees:** Toast: "Server error. Please try again later." ## Best Practices ### ✅ DO ```typescript // Use handleError for simple cases try { await api.post('/vips', data); toast.success('Success'); } catch (error) { handleError('VIP Creation', error); } // Use ErrorHandler methods for complex cases try { await api.updateEvent(id, data); toast.success('Event updated'); } catch (error) { if (ErrorHandler.isConflict(error)) { // Show conflict resolution dialog showConflictDialog(ErrorHandler.getConflicts(error)); } else { ErrorHandler.showError(error); } } // Log errors with context ErrorHandler.log('Event Update', error, { eventId: id, changes: data }); // Use structured logging in backend this.logger.log(`Created VIP: ${vip.name} (ID: ${vip.id})`); this.logger.error(`Failed to create VIP: ${error.message}`, error.stack); ``` ### ❌ DON'T ```typescript // Don't use generic error messages catch (error) { toast.error('An error occurred'); // Too vague! } // Don't ignore errors catch (error) { console.log(error); // Use console.error and proper context } // Don't manually extract error messages catch (error: any) { const msg = error.response?.data?.message || 'Failed'; // Use ErrorHandler.getMessage() toast.error(msg); } // Don't log sensitive data this.logger.log(`User login: ${email} with password ${password}`); // Never log passwords! ``` ## Debugging Tips ### Enable Verbose Logging **Backend:** Set `LOG_LEVEL=debug` in `.env` **Frontend:** Development mode already has verbose logging enabled ### Check Error Context **Backend logs include:** - Timestamp - HTTP method - URL path - Status code - Request params/query/body - User email (if authenticated) **Frontend logs include:** - Timestamp - Context (feature area) - HTTP method - URL - Request/response data - Error type ### Common Issues **"Network error" but backend is running:** - Check CORS configuration - Verify API_URL in `.env` - Check browser DevTools Network tab **"Authentication required" after login:** - Check if token is stored in localStorage - Verify Auth0 configuration - Check if user is approved in database **Validation errors don't show field names:** - Backend DTO needs proper validation decorators - Use class-validator's @IsString(), @IsEmail(), etc. --- **Last Updated:** 2026-01-25 **See also:** [CLAUDE.md](../CLAUDE.md) for general project documentation