273 lines
6.7 KiB
TypeScript
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;
|
|
}
|
|
}
|