erp-suite/apps/shared-libs/core/errors/nestjs-integration.example.ts

273 lines
6.7 KiB
TypeScript

/**
* NestJS Integration Example
*
* This file demonstrates how to integrate the error handling system
* into a NestJS application.
*
* @example Integration Steps:
* 1. Install the global exception filter
* 2. Use custom error classes in your services/controllers
* 3. Configure request ID generation (optional)
*/
import { NestFactory } from '@nestjs/core';
import { Module, Controller, Get, Injectable, Param } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import {
GlobalExceptionFilter,
NotFoundError,
ValidationError,
UnauthorizedError,
BadRequestError,
} from '@erp-suite/core';
// ========================================
// Option 1: Global Filter in main.ts
// ========================================
/**
* Bootstrap function with global exception filter
*/
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Register global exception filter
app.useGlobalFilters(new GlobalExceptionFilter());
// Enable CORS, validation, etc.
app.enableCors();
await app.listen(3000);
}
// ========================================
// Option 2: Provider-based Registration
// ========================================
/**
* App module with provider-based filter registration
*
* This approach allows dependency injection into the filter
*/
@Module({
imports: [],
controllers: [UsersController],
providers: [
UsersService,
// Register filter as a provider
{
provide: APP_FILTER,
useClass: GlobalExceptionFilter,
},
],
})
export class AppModule {}
// ========================================
// Usage in Services
// ========================================
interface User {
id: string;
email: string;
name: string;
}
@Injectable()
export class UsersService {
private users: User[] = [
{ id: '1', email: 'user1@example.com', name: 'User One' },
{ id: '2', email: 'user2@example.com', name: 'User Two' },
];
/**
* Find user by ID - throws NotFoundError if not found
*/
async findById(id: string): Promise<User> {
const user = this.users.find(u => u.id === id);
if (!user) {
throw new NotFoundError('User not found', { userId: id });
}
return user;
}
/**
* Create user - throws ValidationError on invalid data
*/
async create(email: string, name: string): Promise<User> {
// Validate email
if (!email || !email.includes('@')) {
throw new ValidationError('Invalid email address', {
field: 'email',
value: email,
});
}
// Check for duplicate email
const existing = this.users.find(u => u.email === email);
if (existing) {
throw new BadRequestError('Email already exists', {
email,
existingUserId: existing.id,
});
}
const user: User = {
id: String(this.users.length + 1),
email,
name,
};
this.users.push(user);
return user;
}
/**
* Verify user access - throws UnauthorizedError
*/
async verifyAccess(userId: string, token?: string): Promise<void> {
if (!token) {
throw new UnauthorizedError('Access token required');
}
// Token validation logic...
if (token !== 'valid-token') {
throw new UnauthorizedError('Invalid or expired token', {
userId,
});
}
}
}
// ========================================
// Usage in Controllers
// ========================================
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
/**
* GET /users/:id
*
* Returns 200 with user data or 404 if not found
*/
@Get(':id')
async getUser(@Param('id') id: string): Promise<User> {
// Service throws NotFoundError which is automatically
// caught by GlobalExceptionFilter and converted to proper response
return this.usersService.findById(id);
}
/**
* Example error responses:
*
* Success (200):
* {
* "id": "1",
* "email": "user1@example.com",
* "name": "User One"
* }
*
* Not Found (404):
* {
* "statusCode": 404,
* "error": "Not Found",
* "message": "User not found",
* "details": { "userId": "999" },
* "timestamp": "2025-12-12T10:30:00.000Z",
* "path": "/users/999",
* "requestId": "req-123-456"
* }
*/
}
// ========================================
// Request ID Middleware (Optional)
// ========================================
import { Injectable, NestMiddleware } from '@nestjs/core';
import { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';
/**
* Middleware to generate request IDs
*
* Add this to your middleware chain to enable request tracking
*/
@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// Use existing request ID or generate new one
const requestId =
(req.headers['x-request-id'] as string) ||
(req.headers['x-correlation-id'] as string) ||
randomUUID();
// Set in request headers for downstream access
req.headers['x-request-id'] = requestId;
// Include in response headers
res.setHeader('X-Request-ID', requestId);
next();
}
}
// Register in AppModule
import { MiddlewareConsumer, NestModule } from '@nestjs/common';
@Module({
// ... module configuration
})
export class AppModuleWithRequestId implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(RequestIdMiddleware)
.forRoutes('*'); // Apply to all routes
}
}
// ========================================
// Custom Error Examples
// ========================================
/**
* You can also create custom domain-specific errors
*/
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 in service
@Injectable()
export class PaymentService {
async processPayment(userId: string, amount: number): Promise<void> {
const balance = await this.getBalance(userId);
if (balance < amount) {
throw new InsufficientBalanceError(amount, balance);
}
// Process payment...
}
private async getBalance(userId: string): Promise<number> {
// Mock implementation
return 100;
}
}