erp-suite/apps/shared-libs/core/errors
2026-01-04 06:12:11 -06:00
..
base-error.ts Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
error-filter.ts Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
error-middleware.ts Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
express-integration.example.ts Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
http-errors.ts Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
IMPLEMENTATION_SUMMARY.md Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
index.ts Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
INTEGRATION_GUIDE.md Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
nestjs-integration.example.ts Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
QUICK_REFERENCE.md Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
README.md Initial commit - erp-suite 2026-01-04 06:12:11 -06:00
STRUCTURE.md Initial commit - erp-suite 2026-01-04 06:12:11 -06:00

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:

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

// 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

// 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

// 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:

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:

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

{
  "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)

const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new GlobalExceptionFilter());

Option 2: As Provider (Recommended for DI support)

import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: GlobalExceptionFilter,
    },
  ],
})
export class AppModule {}

Using in Controllers/Services

@Injectable()
export class UsersService {
  async findById(id: string): Promise<User> {
    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<User> {
    // Errors are automatically caught and formatted
    return this.usersService.findById(id);
  }
}

Request ID Tracking

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

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

interface ErrorMiddlewareOptions {
  logger?: ErrorLogger;           // Custom logger
  includeStackTrace?: boolean;    // Include stack traces (dev only)
  transformer?: (error, response) => response;  // Custom transformer
}

Custom Logger

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:

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

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

// 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

// Good
throw new NotFoundError('User not found', { userId });

// Avoid
throw new Error('Not found');

2. Include Contextual Details

// 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

// Service layer - throw errors
async findById(id: string): Promise<User> {
  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

// 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:

@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:

@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:

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:

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)

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)

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

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.