Complete rewrite from Express to NestJS with enterprise-grade features: ## Backend Improvements - Migrated from Express to NestJS 11.0.1 with TypeScript - Implemented Prisma ORM 7.3.0 for type-safe database access - Added CASL authorization system replacing role-based guards - Created global exception filters with structured logging - Implemented Auth0 JWT authentication with Passport.js - Added vehicle management with conflict detection - Enhanced event scheduling with driver/vehicle assignment - Comprehensive error handling and logging ## Frontend Improvements - Upgraded to React 19.2.0 with Vite 7.2.4 - Implemented CASL-based permission system - Added AbilityContext for declarative permissions - Created ErrorHandler utility for consistent error messages - Enhanced API client with request/response logging - Added War Room (Command Center) dashboard - Created VIP Schedule view with complete itineraries - Implemented Vehicle Management UI - Added mock data generators for testing (288 events across 20 VIPs) ## New Features - Vehicle fleet management (types, capacity, status tracking) - Complete 3-day Jamboree schedule generation - Individual VIP schedule pages with PDF export (planned) - Real-time War Room dashboard with auto-refresh - Permission-based navigation filtering - First user auto-approval as administrator ## Documentation - Created CASL_AUTHORIZATION.md (comprehensive guide) - Created ERROR_HANDLING.md (error handling patterns) - Updated CLAUDE.md with new architecture - Added migration guides and best practices ## Technical Debt Resolved - Removed custom authentication in favor of Auth0 - Replaced role checks with CASL abilities - Standardized error responses across API - Implemented proper TypeScript typing - Added comprehensive logging Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
8.9 KiB
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:
{
"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:
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
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
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
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
ErrorHandler.showError(error, 'Operation failed');
Log Only (No Toast)
ErrorHandler.log('VIP Fetch', error, { vipId: '123' });
Check Error Type
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)
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)
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
// 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
// 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 for general project documentation