# Error Handling System Standardized error handling for all ERP-Suite backends (NestJS and Express). ## Overview This module provides: - **Base error classes** with consistent structure - **HTTP-specific errors** for common status codes - **NestJS exception filter** for automatic error handling - **Express middleware** for error handling - **Request tracking** with request IDs - **Structured logging** with severity levels ## Installation The error handling module is part of `@erp-suite/core`: ```typescript import { // Base types BaseError, ErrorResponse, // HTTP errors BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, ValidationError, InternalServerError, // NestJS GlobalExceptionFilter, // Express createErrorMiddleware, errorMiddleware, notFoundMiddleware, } from '@erp-suite/core'; ``` ## Quick Start ### NestJS Integration ```typescript // main.ts import { NestFactory } from '@nestjs/core'; import { GlobalExceptionFilter } from '@erp-suite/core'; async function bootstrap() { const app = await NestFactory.create(AppModule); // Register global exception filter app.useGlobalFilters(new GlobalExceptionFilter()); await app.listen(3000); } ``` ### Express Integration ```typescript // index.ts import express from 'express'; import { createErrorMiddleware, notFoundMiddleware } from '@erp-suite/core'; const app = express(); // Your routes here app.use('/api', routes); // 404 handler (before error middleware) app.use(notFoundMiddleware); // Error handler (must be last) app.use(createErrorMiddleware()); app.listen(3000); ``` ## Error Classes ### HTTP Errors All HTTP error classes extend `BaseError` and include: - `statusCode`: HTTP status code - `error`: Error type string - `message`: Human-readable message - `details`: Optional additional context #### Available Error Classes | Class | Status Code | Usage | |-------|-------------|-------| | `BadRequestError` | 400 | Invalid request parameters | | `UnauthorizedError` | 401 | Missing or invalid authentication | | `ForbiddenError` | 403 | Authenticated but insufficient permissions | | `NotFoundError` | 404 | Resource doesn't exist | | `ConflictError` | 409 | Resource conflict (e.g., duplicate) | | `ValidationError` | 422 | Validation failed | | `InternalServerError` | 500 | Unexpected server error | #### Usage Examples ```typescript // Not Found throw new NotFoundError('User not found', { userId: '123' }); // Validation throw new ValidationError('Invalid input', { errors: [ { field: 'email', message: 'Invalid format' }, { field: 'age', message: 'Must be 18+' } ] }); // Unauthorized throw new UnauthorizedError('Invalid token'); // Conflict throw new ConflictError('Email already exists', { email: 'user@example.com' }); ``` ### Custom Domain Errors Create custom errors for your domain: ```typescript import { BaseError } from '@erp-suite/core'; export class InsufficientBalanceError extends BaseError { readonly statusCode = 400; readonly error = 'Insufficient Balance'; constructor(required: number, available: number) { super('Insufficient balance for this operation', { required, available, deficit: required - available, }); } } // Usage throw new InsufficientBalanceError(100, 50); ``` ## Error Response Format All errors are converted to this standardized format: ```typescript interface ErrorResponse { statusCode: number; // HTTP status code error: string; // Error type message: string; // Human-readable message details?: object; // Optional additional context timestamp: string; // ISO 8601 timestamp path?: string; // Request path requestId?: string; // Request tracking ID } ``` ### Example Response ```json { "statusCode": 404, "error": "Not Found", "message": "User not found", "details": { "userId": "999" }, "timestamp": "2025-12-12T10:30:00.000Z", "path": "/api/users/999", "requestId": "550e8400-e29b-41d4-a716-446655440000" } ``` ## NestJS Details ### Global Filter Registration **Option 1: In main.ts** (Recommended for simple cases) ```typescript const app = await NestFactory.create(AppModule); app.useGlobalFilters(new GlobalExceptionFilter()); ``` **Option 2: As Provider** (Recommended for DI support) ```typescript import { APP_FILTER } from '@nestjs/core'; @Module({ providers: [ { provide: APP_FILTER, useClass: GlobalExceptionFilter, }, ], }) export class AppModule {} ``` ### Using in Controllers/Services ```typescript @Injectable() export class UsersService { async findById(id: string): Promise { const user = await this.repository.findOne({ where: { id } }); if (!user) { throw new NotFoundError('User not found', { userId: id }); } return user; } } @Controller('users') export class UsersController { @Get(':id') async getUser(@Param('id') id: string): Promise { // Errors are automatically caught and formatted return this.usersService.findById(id); } } ``` ### Request ID Tracking ```typescript import { Injectable, NestMiddleware } from '@nestjs/common'; import { randomUUID } from 'crypto'; @Injectable() export class RequestIdMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { const requestId = req.headers['x-request-id'] || randomUUID(); req.headers['x-request-id'] = requestId; res.setHeader('X-Request-ID', requestId); next(); } } ``` ## Express Details ### Middleware Setup ```typescript import express from 'express'; import { createErrorMiddleware, notFoundMiddleware, ErrorLogger, } from '@erp-suite/core'; const app = express(); // Body parsing app.use(express.json()); // Your routes app.use('/api/users', usersRouter); // 404 handler (optional but recommended) app.use(notFoundMiddleware); // Error middleware (MUST be last) app.use(createErrorMiddleware({ logger: customLogger, includeStackTrace: process.env.NODE_ENV !== 'production', })); ``` ### Configuration Options ```typescript interface ErrorMiddlewareOptions { logger?: ErrorLogger; // Custom logger includeStackTrace?: boolean; // Include stack traces (dev only) transformer?: (error, response) => response; // Custom transformer } ``` ### Custom Logger ```typescript import { ErrorLogger } from '@erp-suite/core'; class CustomLogger implements ErrorLogger { error(message: string, ...meta: any[]): void { winston.error(message, ...meta); } warn(message: string, ...meta: any[]): void { winston.warn(message, ...meta); } log(message: string, ...meta: any[]): void { winston.info(message, ...meta); } } app.use(createErrorMiddleware({ logger: new CustomLogger(), })); ``` ### Async Route Handlers Use an async handler wrapper to automatically catch errors: ```typescript function asyncHandler(fn) { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; } app.get('/users/:id', asyncHandler(async (req, res) => { const user = await findUserById(req.params.id); res.json(user); })); ``` ### Using in Routes ```typescript const router = express.Router(); router.get('/:id', async (req, res, next) => { try { const user = await usersService.findById(req.params.id); res.json(user); } catch (error) { next(error); // Pass to error middleware } }); ``` ## Logging ### Log Levels Errors are automatically logged with appropriate severity: - **ERROR** (500+): Server errors, unexpected errors - **WARN** (400-499): Client errors, validation failures - **INFO** (<400): Informational messages ### Log Format ```typescript // Error log [500] Internal Server Error: Database connection failed { "path": "/api/users", "requestId": "req-123", "details": { ... }, "stack": "Error: ...\n at ..." } // Warning log [404] Not Found: User not found { "path": "/api/users/999", "requestId": "req-124", "details": { "userId": "999" } } ``` ## Best Practices ### 1. Use Specific Error Classes ```typescript // Good throw new NotFoundError('User not found', { userId }); // Avoid throw new Error('Not found'); ``` ### 2. Include Contextual Details ```typescript // Good - includes helpful context throw new ValidationError('Validation failed', { errors: [ { field: 'email', message: 'Invalid format' }, { field: 'password', message: 'Too short' } ] }); // Less helpful throw new ValidationError('Invalid input'); ``` ### 3. Throw Early, Handle Centrally ```typescript // Service layer - throw errors async findById(id: string): Promise { const user = await this.repository.findOne(id); if (!user) { throw new NotFoundError('User not found', { userId: id }); } return user; } // Controller/Route - let filter/middleware handle @Get(':id') async getUser(@Param('id') id: string) { return this.service.findById(id); // Don't try/catch here } ``` ### 4. Don't Expose Internal Details in Production ```typescript // Good throw new InternalServerError('Database operation failed'); // Avoid in production throw new InternalServerError('Connection to PostgreSQL at 10.0.0.5:5432 failed'); ``` ### 5. Use Request IDs for Tracking Always include request ID middleware to enable request tracing across logs. ## Migration Guide ### From Manual Error Handling (NestJS) **Before:** ```typescript @Get(':id') async getUser(@Param('id') id: string) { try { const user = await this.service.findById(id); return user; } catch (error) { throw new HttpException('User not found', HttpStatus.NOT_FOUND); } } ``` **After:** ```typescript @Get(':id') async getUser(@Param('id') id: string) { return this.service.findById(id); // Service throws NotFoundError } // In service async findById(id: string) { const user = await this.repository.findOne(id); if (!user) { throw new NotFoundError('User not found', { userId: id }); } return user; } ``` ### From Manual Error Handling (Express) **Before:** ```typescript app.get('/users/:id', async (req, res) => { try { const user = await findUser(req.params.id); res.json(user); } catch (error) { res.status(500).json({ error: 'Internal error' }); } }); ``` **After:** ```typescript app.get('/users/:id', asyncHandler(async (req, res) => { const user = await findUser(req.params.id); // Throws NotFoundError res.json(user); })); // Error middleware handles it automatically ``` ## Testing ### Testing Error Responses (NestJS) ```typescript it('should return 404 when user not found', async () => { const response = await request(app.getHttpServer()) .get('/users/999') .expect(404); expect(response.body).toMatchObject({ statusCode: 404, error: 'Not Found', message: 'User not found', details: { userId: '999' }, }); expect(response.body.timestamp).toBeDefined(); expect(response.body.path).toBe('/users/999'); }); ``` ### Testing Error Responses (Express) ```typescript it('should return 404 when user not found', async () => { const response = await request(app) .get('/api/users/999') .expect(404); expect(response.body).toMatchObject({ statusCode: 404, error: 'Not Found', message: 'User not found', }); }); ``` ### Testing Custom Errors ```typescript describe('InsufficientBalanceError', () => { it('should create error with correct details', () => { const error = new InsufficientBalanceError(100, 50); expect(error.statusCode).toBe(400); expect(error.message).toBe('Insufficient balance for this operation'); expect(error.details).toEqual({ required: 100, available: 50, deficit: 50, }); }); }); ``` ## Examples See detailed integration examples: - **NestJS**: `nestjs-integration.example.ts` - **Express**: `express-integration.example.ts` ## Files ``` errors/ ├── base-error.ts # Base error class and types ├── http-errors.ts # HTTP-specific error classes ├── error-filter.ts # NestJS exception filter ├── error-middleware.ts # Express error middleware ├── index.ts # Module exports ├── README.md # This file ├── nestjs-integration.example.ts # NestJS examples └── express-integration.example.ts # Express examples ``` ## Support For questions or issues, contact the ERP-Suite development team.