Skip to main content

Exception Handling

The nestjs-multitenant package provides a comprehensive exception handling system with structured error responses, custom error classes, and support for both Express and Fastify adapters.

Overview

The exception handling system consists of:

  • MultitenantExceptionFilter: Global exception filter that catches all errors
  • Custom Error Classes: Domain-specific error classes
  • ErrorTypeMapper: Maps different error types to standardized responses
  • StructuredLogger: JSON-structured logging with context

Installation

The exception handling is included in the main package. No additional installation required:

npm install nestjs-multitenant

Quick Setup

1. Register Globally

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { MultitenantExceptionFilter } from 'nestjs-multitenant';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

// Register the exception filter globally
app.useGlobalFilters(new MultitenantExceptionFilter());

await app.listen(3000);
}
bootstrap();

2. Register via Module

// app.module.ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { MultitenantExceptionFilter } from 'nestjs-multitenant';

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

Custom Error Classes

The package provides domain-specific custom error classes:

import {
NoTenantContextError,
InvalidTenantCodeError,
SchemaNotFoundError,
TenantValidationError,
TenantConflictError,
ConnectionPoolExhaustedError,
InvalidConnectionTypeError,
TransactionFailedError,
} from 'nestjs-multitenant';

Usage Examples

@Injectable()
export class TenantService {
async createTenant(tenantCode: string) {
// Throw when tenant context is required but not available
if (!this.hasTenantContext()) {
throw new NoTenantContextError(
'Tenant context required for this operation',
);
}

// Throw when tenant code is invalid
if (!this.isValidTenantCode(tenantCode)) {
throw new InvalidTenantCodeError(tenantCode);
}

// Throw when schema doesn't exist
if (!(await this.schemaExists(tenantCode))) {
throw new SchemaNotFoundError(tenantCode);
}

// Throw on validation failures
const errors = this.validateTenantData(data);
if (errors.length > 0) {
throw new TenantValidationError(errors);
}

// Throw on conflict (tenant already exists)
if (await this.tenantExists(tenantCode)) {
throw new TenantConflictError(tenantCode);
}
}
}

Error Class Reference

Error ClassHTTP StatusUse Case
NoTenantContextError400Missing tenant context
InvalidTenantCodeError404Invalid tenant identifier
SchemaNotFoundError404Tenant schema not found
TenantValidationError400Validation failed
TenantConflictError409Tenant already exists
ConnectionPoolExhaustedError503Pool exhausted
InvalidConnectionTypeError400Invalid connection type
TransactionFailedError500Transaction failed

Structured Error Responses

All errors return a consistent JSON format:

{
"success": false,
"error": {
"code": "TENANT_NOT_FOUND",
"message": "Tenant schema not found",
"category": "TENANT",
"statusCode": 404,
"timestamp": "2024-01-15T10:30:00.000Z",
"traceId": "mt_1705315800000_abc123def",
"details": {
"tenant": {
"invalidCode": "unknown_tenant"
}
},
"request": {
"method": "GET",
"url": "/api/users",
"tenantCode": "unknown_tenant"
}
}
}

Response Fields

FieldTypeDescription
successbooleanAlways false for errors
error.codestringMachine-readable error code
error.messagestringHuman-readable message
error.categorystringError category
error.statusCodenumberHTTP status code
error.timestampstringISO timestamp
error.traceIdstringUnique trace ID
error.detailsobjectAdditional error details
error.requestobjectRequest context

Error Categories

  • DATABASE: TypeORM and Drizzle errors (PostgreSQL)
  • TENANT: Tenant-specific errors
  • CONNECTION: Connection and pool errors
  • VALIDATION: Data validation errors
  • SYSTEM: Internal server errors

Database Error Mapping

The filter automatically maps database errors:

// These PostgreSQL errors are automatically mapped:
- 23505: Unique constraint violation (409)
- 23503: Foreign key violation (400)
- 23502: Not null violation (400)
- 23514: Check constraint violation (400)
- 40001: Serialization failure (409)
- 08006: Connection failure (503)
- 53300: Pool exhausted (503)
- 42P01: Undefined table (404)

Response Headers

The filter adds correlation headers:

X-Trace-ID: mt_1705315800000_abc123def
X-Tenant-Code: tenant1

Logging

The StructuredLogger provides JSON-formatted logging:

import { StructuredLogger, LogContext } from 'nestjs-multitenant';

const logger = new StructuredLogger();
const context: LogContext = {
traceId: 'mt_1705315800000_abc123def',
tenantCode: 'tenant1',
method: 'GET',
url: '/api/users',
timestamp: new Date().toISOString(),
};

// Log errors with full context
logger.logError(error, context, {
error: {
name: error.name,
message: error.message,
category: 'TENANT',
errorCode: 'SCHEMA_NOT_FOUND',
statusCode: 404,
},
tenant: {
code: 'tenant1',
},
});

Integration with NestJS Exceptions

The filter handles standard NestJS exceptions:

// These are also handled automatically
throw new NotFoundException('Resource not found');
throw new BadRequestException('Invalid input');
throw new UnauthorizedException('Unauthorized');
throw new ForbiddenException('Forbidden');
throw new ConflictException('Conflict');

Suppress Sensitive Information

System errors are sanitized to prevent information disclosure:

// This returns a generic message
throw new Error('Database connection string: postgres://secret...');

// Response:
// {
// "success": false,
// "error": {
// "code": "INTERNAL_SERVER_ERROR",
// "message": "An internal server error occurred. Please try again later.",
// "category": "SYSTEM",
// ...
// }
// }

Fastify Support

The filter automatically detects and supports Fastify:

// No additional configuration needed
// The filter uses type guards to detect the adapter

async function bootstrap() {
const app = await NestFactory.create(AppModule, new FastifyAdapter());
app.useGlobalFilters(new MultitenantExceptionFilter());
await app.listen(3000);
}

Next Steps