Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/common/filters/all-exceptions.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpStatus,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
private readonly logger = new Logger(AllExceptionsFilter.name);

catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();

const status = HttpStatus.INTERNAL_SERVER_ERROR;
const message = exception instanceof Error ? exception.message : 'Internal server error';

this.logger.error(
`Uncaught Exception: ${message} - ${request.url}`,
exception instanceof Error ? exception.stack : 'No stack trace available',
);

response.status(status).json({
success: false,
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: process.env.NODE_ENV === 'production' ? 'Internal server error' : message,
stack: process.env.NODE_ENV === 'development' && exception instanceof Error ? exception.stack : undefined,
});
}
}
45 changes: 45 additions & 0 deletions src/common/filters/http-exception.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(HttpExceptionFilter.name);

catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();

const exceptionResponse = exception.getResponse();
let message = 'An unexpected error occurred';
let errors = null;

if (typeof exceptionResponse === 'string') {
message = exceptionResponse;
} else if (typeof exceptionResponse === 'object') {
message = (exceptionResponse as any).message || message;
errors = (exceptionResponse as any).errors || null;
}

this.logger.error(
`HTTP Exception: ${status} - ${message} - ${request.url} - Stack: ${exception.stack}`,
);

response.status(status).json({
success: false,
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
errors,
stack: process.env.NODE_ENV === 'development' ? exception.stack : undefined,
});
}
}
71 changes: 71 additions & 0 deletions src/common/filters/prisma-exception.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpStatus,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { Prisma } from '@prisma/client';

@Catch(Prisma.PrismaClientKnownRequestError)
export class PrismaExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(PrismaExceptionFilter.name);

catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();

let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Database error occurred';
let errors = null;

// Handle common Prisma errors
switch (exception.code) {
case 'P2002': // Unique constraint violation
status = HttpStatus.CONFLICT;
const target = (exception.meta?.target as string[])?.join(', ') || 'field';
message = `Unique constraint failed on ${target}`;
errors = { [target]: 'must be unique' };
break;
case 'P2025': // Record not found
status = HttpStatus.NOT_FOUND;
message = 'Record not found';
break;
case 'P2003': // Foreign key constraint violation
status = HttpStatus.BAD_REQUEST;
message = 'Foreign key constraint failed - related record does not exist';
break;
case 'P2014': // Relation violation
status = HttpStatus.BAD_REQUEST;
message = 'Invalid relation - cannot change record due to existing dependencies';
break;
case 'P2000': // Value too long for column
status = HttpStatus.BAD_REQUEST;
message = 'Value too long for column';
break;
case 'P2011': // Null constraint violation
status = HttpStatus.BAD_REQUEST;
message = 'Null constraint violation - cannot set required field to null';
break;
default:
this.logger.error(`Unhandled Prisma error: ${exception.code}`, exception.stack);
}

this.logger.error(
`Prisma Exception: ${exception.code} - ${message} - ${request.url} - Stack: ${exception.stack}`,
);

response.status(status).json({
success: false,
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
errors,
prismaCode: process.env.NODE_ENV === 'development' ? exception.code : undefined,
stack: process.env.NODE_ENV === 'development' ? exception.stack : undefined,
});
}
}
4 changes: 4 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import { RateLimitHeadersInterceptor } from './auth/interceptors/rate-limit-head
import { ResponseFormatInterceptor } from './common/interceptors/response-format.interceptor';
import { setupSwagger } from './config/swagger.config';
import { validateEnvironment } from './utils/validate-env';
// Import our exception filters
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { PrismaExceptionFilter } from './common/filters/prisma-exception.filter';

async function bootstrap() {
validateEnvironment();
Expand Down
Loading
Loading