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:
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:
- Checks for
x-correlation-id
header in incoming requests - Generates a UUID if header is missing
- Adds correlation ID to all error responses
- Adds timestamp to error responses
- 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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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
- Validation - Handle validation errors
- Subscriber System - Error handling in subscribers
- API Reference - Interceptors - CorrelationID interceptor reference