Skip to Content

Error Handling

NestJS CRUD Automator provides structured error handling with CorrelationID tracking and standardized error responses.

CorrelationID Interceptor

The library includes a built-in interceptor for tracking requests with correlation IDs:

main.ts
import { NestFactory } from "@nestjs/core"; import { CorrelationIDResponseBodyInterceptor } from "@elsikora/nestjs-crud-automator"; import { AppModule } from "./app.module"; async function bootstrap() { const app = await NestFactory.create(AppModule); // Register globally app.useGlobalInterceptors(new CorrelationIDResponseBodyInterceptor()); await app.listen(3000); } bootstrap();

How It Works

The interceptor:

  1. Checks for x-correlation-id header in incoming requests
  2. Generates a UUID if header is missing
  3. Adds correlation ID to all error responses
  4. Adds timestamp to error responses
  5. Handles HttpException, ThrottlerException, and generic errors

Error Response Format

Standard error response structure:

{ "statusCode": 400, "message": "Validation failed", "error": "Bad Request", "timestamp": 1704067200000, "correlationID": "550e8400-e29b-41d4-a716-446655440000" }

Using Correlation IDs

Send correlation ID from client:

frontend-example.ts
const response = await fetch("/users", { method: "POST", headers: { "Content-Type": "application/json", "x-correlation-id": "client-generated-uuid", }, body: JSON.stringify(userData), });

The same ID will appear in error responses and server logs, enabling request tracing.

Validation Errors

Automatic validation error handling:

{ "statusCode": 400, "message": [ "username must be longer than or equal to 3 characters", "email must be an email" ], "error": "Bad Request", "timestamp": 1704067200000, "correlationID": "550e8400-e29b-41d4-a716-446655440000" }

Custom Exceptions

Create custom exceptions for specific errors:

exceptions/user-not-found.exception.ts
import { NotFoundException } from "@nestjs/common"; export class UserNotFoundException extends NotFoundException { constructor(userId: string) { super({ message: `User with ID ${userId} not found`, code: "USER_NOT_FOUND", }); } }

Use in services:

user.service.ts
async findUserById(id: string): Promise<UserEntity> { const user = await this.repository.findOne({ where: { id } }); if (!user) { throw new UserNotFoundException(id); } return user; }

Error Handling in Subscribers

Handle errors in subscriber hooks:

post-error.subscriber.ts
import { Injectable } from "@nestjs/common"; import { ApiRouteSubscriber, ApiRouteSubscriberBase, IApiSubscriberRouteErrorExecutionContext } from "@elsikora/nestjs-crud-automator"; import { PostEntity } from "./post.entity"; @Injectable() @ApiRouteSubscriber({ entity: PostEntity, priority: 10 }) export class PostErrorSubscriber extends ApiRouteSubscriberBase<PostEntity> { async onAfterErrorCreate( context: IApiSubscriberRouteErrorExecutionContext<PostEntity>, error: Error ): Promise<void> { // Log error with context console.error("Post creation failed:", { error: error.message, data: context.DATA, entity: context.ENTITY, }); // Notify admins, send alerts, etc. await this.notifyAdmins(error); } async onBeforeErrorCreate( context: IApiSubscriberRouteErrorExecutionContext<PostEntity>, error: Error ): Promise<void> { // Perform cleanup before error propagates await this.cleanup(context); } private async notifyAdmins(error: Error): Promise<void> { // Send notification logic } private async cleanup( context: IApiSubscriberRouteErrorExecutionContext<PostEntity> ): Promise<void> { // Cleanup logic } }

Global Exception Filter

Create a global exception filter for custom error handling:

filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, } from "@nestjs/common"; import { FastifyReply, FastifyRequest } from "fastify"; @Catch() export class GlobalExceptionFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<FastifyReply>(); const request = ctx.getRequest<FastifyRequest>(); const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; const message = exception instanceof HttpException ? exception.message : "Internal server error"; const correlationId = request.headers["x-correlation-id"] || crypto.randomUUID(); response.status(status).send({ statusCode: status, message, timestamp: new Date().toISOString(), path: request.url, correlationID: correlationId, }); } }

Register globally:

main.ts
import { GlobalExceptionFilter } from "./filters/http-exception.filter"; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new GlobalExceptionFilter()); await app.listen(3000); }

Logging Errors

Integrate with logging service:

services/logger.service.ts
import { Injectable, LoggerService } from "@nestjs/common"; @Injectable() export class CustomLoggerService implements LoggerService { log(message: string, context?: string) { // Log with correlation ID console.log(`[${context}] ${message}`); } error(message: string, trace?: string, context?: string) { console.error(`[${context}] ${message}`, trace); } warn(message: string, context?: string) { console.warn(`[${context}] ${message}`); } }

Use in error handling:

constructor(private readonly logger: CustomLoggerService) {} async onAfterErrorCreate( context: IApiSubscriberRouteErrorExecutionContext<PostEntity>, error: Error ): Promise<void> { this.logger.error( `Post creation failed: ${error.message}`, error.stack, "PostErrorSubscriber" ); }

Business Logic Errors

Create domain-specific exceptions:

exceptions/business-errors.ts
import { BadRequestException, ForbiddenException } from "@nestjs/common"; export class InsufficientBalanceException extends BadRequestException { constructor(required: number, available: number) { super({ message: "Insufficient balance for this operation", code: "INSUFFICIENT_BALANCE", required, available, }); } } export class ResourceLockedException extends ForbiddenException { constructor(resourceId: string) { super({ message: "Resource is locked and cannot be modified", code: "RESOURCE_LOCKED", resourceId, }); } } export class DuplicateResourceException extends BadRequestException { constructor(field: string, value: string) { super({ message: `${field} already exists`, code: "DUPLICATE_RESOURCE", field, value, }); } }

Rate Limiting Errors

Handle throttler exceptions:

main.ts
import { ThrottlerModule } from "@nestjs/throttler"; @Module({ imports: [ ThrottlerModule.forRoot({ ttl: 60, limit: 10, }), ], }) export class AppModule {}

The CorrelationIDResponseBodyInterceptor automatically handles ThrottlerException:

{ "statusCode": 429, "message": "Too Many Requests", "error": "Too Many Requests", "timestamp": 1704067200000, "correlationID": "550e8400-e29b-41d4-a716-446655440000" }

Database Errors

Handle database constraint violations:

services/user.service.ts
import { Injectable, ConflictException } from "@nestjs/common"; import { QueryFailedError } from "typeorm"; @Injectable() export class UserService extends ApiServiceBase<UserEntity> { async create(properties: any): Promise<UserEntity> { try { return await super.create(properties); } catch (error) { if (error instanceof QueryFailedError) { // PostgreSQL unique violation if (error.driverError?.code === "23505") { throw new ConflictException("Username or email already exists"); } // PostgreSQL foreign key violation if (error.driverError?.code === "23503") { throw new BadRequestException("Referenced resource does not exist"); } } throw error; } } }

Retry Logic

Implement retry logic for transient errors:

utils/retry.util.ts
export async function retry<T>( fn: () => Promise<T>, maxAttempts: number = 3, delay: number = 1000 ): Promise<T> { let lastError: Error; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; if (attempt < maxAttempts) { await new Promise(resolve => setTimeout(resolve, delay * attempt)); } } } throw lastError!; }

Use in services:

async fetchExternalData(id: string): Promise<Data> { return retry(async () => { const response = await fetch(`https://api.example.com/data/${id}`); if (!response.ok) { throw new Error("Failed to fetch data"); } return response.json(); }); }

Next Steps

Last updated on