/** * Express Integration Example * * This file demonstrates how to integrate the error handling system * into an Express application. * * @example Integration Steps: * 1. Set up error middleware (must be last) * 2. Optionally add 404 handler * 3. Use custom error classes in your routes * 4. Configure request ID generation (optional) */ import express, { Request, Response, NextFunction, Router } from 'express'; import { createErrorMiddleware, notFoundMiddleware, NotFoundError, ValidationError, UnauthorizedError, BadRequestError, BaseError, } from '@erp-suite/core'; // ======================================== // Basic Express App Setup // ======================================== /** * Create Express app with error handling */ function createApp() { const app = express(); // Body parsing middleware app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Request ID middleware (optional but recommended) app.use(requestIdMiddleware); // Your routes app.use('/api/users', usersRouter); app.use('/api/products', productsRouter); // 404 handler (must be after all routes) app.use(notFoundMiddleware); // Error handling middleware (must be last) app.use(createErrorMiddleware({ includeStackTrace: process.env.NODE_ENV !== 'production', })); return app; } // ======================================== // Request ID Middleware (Optional) // ======================================== import { randomUUID } from 'crypto'; /** * Middleware to generate and track request IDs */ function requestIdMiddleware(req: Request, res: Response, next: NextFunction) { const requestId = (req.headers['x-request-id'] as string) || (req.headers['x-correlation-id'] as string) || randomUUID(); // Attach to request for access in handlers (req as any).requestId = requestId; // Include in response headers res.setHeader('X-Request-ID', requestId); next(); } // ======================================== // Custom Logger Integration // ======================================== import { ErrorLogger } from '@erp-suite/core'; /** * Custom logger implementation (e.g., Winston, Pino) */ class WinstonLogger implements ErrorLogger { error(message: string, ...meta: any[]): void { // winston.error(message, ...meta); console.error('[ERROR]', message, ...meta); } warn(message: string, ...meta: any[]): void { // winston.warn(message, ...meta); console.warn('[WARN]', message, ...meta); } log(message: string, ...meta: any[]): void { // winston.info(message, ...meta); console.log('[INFO]', message, ...meta); } } /** * App with custom logger */ function createAppWithCustomLogger() { const app = express(); app.use(express.json()); app.use('/api/users', usersRouter); // Error middleware with custom logger app.use(createErrorMiddleware({ logger: new WinstonLogger(), includeStackTrace: process.env.NODE_ENV !== 'production', })); return app; } // ======================================== // Users Router Example // ======================================== interface User { id: string; email: string; name: string; } // Mock database const users: User[] = [ { id: '1', email: 'user1@example.com', name: 'User One' }, { id: '2', email: 'user2@example.com', name: 'User Two' }, ]; const usersRouter = Router(); /** * GET /api/users/:id * * Demonstrates NotFoundError handling */ usersRouter.get('/:id', (req: Request, res: Response, next: NextFunction) => { try { const user = users.find(u => u.id === req.params.id); if (!user) { throw new NotFoundError('User not found', { userId: req.params.id }); } res.json(user); } catch (error) { next(error); // Pass to error middleware } }); /** * POST /api/users * * Demonstrates ValidationError handling */ usersRouter.post('/', (req: Request, res: Response, next: NextFunction) => { try { const { email, name } = req.body; // Validation const errors: any[] = []; if (!email || !email.includes('@')) { errors.push({ field: 'email', message: 'Valid email is required' }); } if (!name || name.length < 2) { errors.push({ field: 'name', message: 'Name must be at least 2 characters' }); } if (errors.length > 0) { throw new ValidationError('Validation failed', { errors }); } // Check for duplicate email const existing = users.find(u => u.email === email); if (existing) { throw new BadRequestError('Email already exists', { email, existingUserId: existing.id, }); } // Create user const user: User = { id: String(users.length + 1), email, name, }; users.push(user); res.status(201).json(user); } catch (error) { next(error); } }); /** * Async/await route handler with error handling */ usersRouter.get( '/:id/profile', asyncHandler(async (req: Request, res: Response) => { const user = await findUserById(req.params.id); const profile = await getUserProfile(user.id); res.json(profile); }) ); // ======================================== // Async Handler Wrapper // ======================================== /** * Wraps async route handlers to automatically catch errors * * Usage: app.get('/route', asyncHandler(async (req, res) => { ... })) */ function asyncHandler( fn: (req: Request, res: Response, next: NextFunction) => Promise ) { return (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); }; } // ======================================== // Products Router Example // ======================================== const productsRouter = Router(); /** * Protected route example */ productsRouter.get( '/', authMiddleware, // Authentication middleware asyncHandler(async (req: Request, res: Response) => { // This route is protected and will throw UnauthorizedError // if authentication fails const products = await getProducts(); res.json(products); }) ); /** * Authentication middleware example */ function authMiddleware(req: Request, res: Response, next: NextFunction) { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { throw new UnauthorizedError('Authentication token required'); } // Validate token... if (token !== 'valid-token') { throw new UnauthorizedError('Invalid or expired token', { providedToken: token.substring(0, 10) + '...', }); } // Attach user to request (req as any).user = { id: '1', email: 'user@example.com' }; next(); } // ======================================== // Service Layer Example // ======================================== /** * Service layer with error handling */ class UserService { async findById(id: string): Promise { const user = users.find(u => u.id === id); if (!user) { throw new NotFoundError('User not found', { userId: id }); } return user; } async create(email: string, name: string): Promise { // Validation if (!email || !email.includes('@')) { throw new ValidationError('Invalid email address', { field: 'email', value: email, }); } // Business logic validation const existing = users.find(u => u.email === email); if (existing) { throw new BadRequestError('Email already exists', { email, existingUserId: existing.id, }); } const user: User = { id: String(users.length + 1), email, name, }; users.push(user); return user; } } // ======================================== // Custom Domain Errors // ======================================== /** * Create custom errors for your domain */ 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, }); } } class PaymentService { async processPayment(userId: string, amount: number): Promise { const balance = await this.getBalance(userId); if (balance < amount) { throw new InsufficientBalanceError(amount, balance); } // Process payment... } private async getBalance(userId: string): Promise { return 100; // Mock } } // ======================================== // Error Response Examples // ======================================== /** * Example error responses generated by the system: * * 404 Not Found: * GET /api/users/999 * { * "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" * } * * 422 Validation Error: * POST /api/users { "email": "invalid", "name": "A" } * { * "statusCode": 422, * "error": "Validation Error", * "message": "Validation failed", * "details": { * "errors": [ * { "field": "email", "message": "Valid email is required" }, * { "field": "name", "message": "Name must be at least 2 characters" } * ] * }, * "timestamp": "2025-12-12T10:30:00.000Z", * "path": "/api/users", * "requestId": "550e8400-e29b-41d4-a716-446655440001" * } * * 401 Unauthorized: * GET /api/products (without token) * { * "statusCode": 401, * "error": "Unauthorized", * "message": "Authentication token required", * "timestamp": "2025-12-12T10:30:00.000Z", * "path": "/api/products", * "requestId": "550e8400-e29b-41d4-a716-446655440002" * } * * 400 Bad Request: * POST /api/users { "email": "existing@example.com", "name": "Test" } * { * "statusCode": 400, * "error": "Bad Request", * "message": "Email already exists", * "details": { * "email": "existing@example.com", * "existingUserId": "1" * }, * "timestamp": "2025-12-12T10:30:00.000Z", * "path": "/api/users", * "requestId": "550e8400-e29b-41d4-a716-446655440003" * } */ // ======================================== // Helper Functions (Mock) // ======================================== async function findUserById(id: string): Promise { const user = users.find(u => u.id === id); if (!user) { throw new NotFoundError('User not found', { userId: id }); } return user; } async function getUserProfile(userId: string): Promise { return { userId, bio: 'User bio', avatar: 'avatar.jpg' }; } async function getProducts(): Promise { return [ { id: '1', name: 'Product 1', price: 100 }, { id: '2', name: 'Product 2', price: 200 }, ]; } // ======================================== // Start Server // ======================================== if (require.main === module) { const app = createApp(); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); } export { createApp, createAppWithCustomLogger, asyncHandler };