NestJS Prisma Global Exception Filter Guide 2026
- Devin Rosario
- Nov 20, 2025
- 4 min read

Your API returns 500 Internal Server Error for everything. Unique constraint violation? 500. Record not found? 500. Foreign key failure? Still 500. Users see nothing helpful, developers get zero context, and debugging production becomes guesswork.
This happens because Prisma throws PrismaClientKnownRequestError exceptions that NestJS doesn't handle by default. The framework catches them but treats every database error identically. You need proper global exception filters that map each Prisma error code to appropriate HTTP status codes.
Project Structure
Standard NestJS project with Prisma:
src/
├── app.module.ts
├── main.ts
├── filters/
│ └── prisma-exception.filter.ts
└── prisma/
└── prisma.service.ts
The filter goes in src/filters/. Some teams use src/common/filters/. Pick one structure and stick with it.
Building the Exception Filter
Create src/filters/prisma-exception.filter.ts:
import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { Prisma } from '@prisma/client';
import { Response } from 'express';
@Catch(Prisma.PrismaClientKnownRequestError)
export class PrismaExceptionFilter extends BaseExceptionFilter {
catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
console.error('Prisma Error:', exception.code, exception.message);
switch (exception.code) {
case 'P2002': {
const status = HttpStatus.CONFLICT;
response.status(status).json({
statusCode: status,
message: 'A record with this value already exists',
error: 'Conflict',
});
break;
}
case 'P2025': {
const status = HttpStatus.NOT_FOUND;
response.status(status).json({
statusCode: status,
message: 'Record not found',
error: 'Not Found',
});
break;
}
case 'P2003': {
const status = HttpStatus.BAD_REQUEST;
response.status(status).json({
statusCode: status,
message: 'Foreign key constraint failed',
error: 'Bad Request',
});
break;
}
case 'P2014': {
const status = HttpStatus.BAD_REQUEST;
response.status(status).json({
statusCode: status,
message: 'Required relation violation',
error: 'Bad Request',
});
break;
}
default:
super.catch(exception, host);
break;
}
}
}
The console.error logs errors during development. Production apps should pipe these to Sentry or DataDog. The switch statement handles the four most common Prisma errors covering 90% of production cases.
Apply the Filter Globally
Update src/main.ts:
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { PrismaExceptionFilter } from './filters/prisma-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new PrismaExceptionFilter(httpAdapter));
await app.listen(3000);
}
bootstrap();
The HttpAdapterHost provides access to the underlying HTTP framework. Without it, the filter breaks. Projects using Fastify instead of Express need the same setup—the adapter handles framework differences automatically.
Test Your Error Handling
Write integration tests verifying correct status codes. Create test/error-handling.e2e-spec.ts:
import { Test } from '@nestjs/testing';
import { INestApplication, HttpStatus } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Error Handling (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
it('returns 409 for unique constraint violations', async () => {
await request(app.getHttpServer())
.post('/users')
.send({ email: 'test@example.com', name: 'Test' })
.expect(HttpStatus.CREATED);
return request(app.getHttpServer())
.post('/users')
.send({ email: 'test@example.com', name: 'Duplicate' })
.expect(HttpStatus.CONFLICT);
});
it('returns 404 for missing records', async () => {
return request(app.getHttpServer())
.patch('/users/99999')
.send({ name: 'Updated' })
.expect(HttpStatus.NOT_FOUND);
});
afterAll(async () => {
await app.close();
});
});
Tests catch regressions. Someone refactors error handling, breaks status codes, tests fail immediately.
Environment-Specific Error Messages
Development needs detailed errors. Production needs sanitized messages:
catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment) {
console.error('Prisma Error Details:', {
code: exception.code,
meta: exception.meta,
message: exception.message,
});
}
const message = isDevelopment
? exception.message
: 'A database error occurred';
// Handle specific error codes...
}
Development logs include exception.meta showing which fields caused violations. Production hides this, keeping database schema details private.
Using the nestjs-prisma Package
Building filters manually teaches fundamentals. Production projects benefit from battle-tested packages. The nestjs-prisma library handles error mapping automatically:
npm install nestjs-prisma
Configure in app.module.ts:
import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';
@Module({
imports: [
PrismaModule.forRoot({
isGlobal: true,
prismaServiceOptions: {
prismaOptions: {
log: ['error', 'warn'],
},
explicitConnect: true,
},
}),
],
})
export class AppModule {}
Apply the pre-built filter in main.ts:
import { PrismaClientExceptionFilter } from 'nestjs-prisma';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new PrismaClientExceptionFilter(httpAdapter));
await app.listen(3000);
}
The package maintains error code mappings, handles edge cases, supports REST and GraphQL, and receives updates when Prisma adds new error codes. Teams working on mobile app development in Maryland benefit from consistent error handling across backend APIs.
Troubleshooting Common Issues
Filter not catching errors?
Check three things:
Filter registered globally in main.ts?
Passing HttpAdapterHost to filter constructor?
Using await on Prisma queries?
Still getting 500 errors? Add logging inside the filter's catch method. If logs appear, the filter works but status code mapping failed. If logs never appear, registration problem.
Different error types need different filters. Validation errors from class-validator require separate handling. Authentication failures need their own filter. Database connection issues during startup throw PrismaClientInitializationError, not PrismaClientKnownRequestError.
Before and After Comparison
Without proper error handling:
{
"statusCode": 500,
"message": "Internal server error"
}
With global exception filter:
{
"statusCode": 409,
"message": "A record with this value already exists",
"error": "Conflict"
}
The difference transforms user experience. Signup fails with clear feedback instead of generic errors. Support teams spend less time debugging. Monitoring dashboards separate user errors (400s) from system failures (500s).
Implementation Checklist
Create PrismaExceptionFilter in src/filters/
Import dependencies: @nestjs/common, @nestjs/core, @prisma/client
Extend BaseExceptionFilter for fallback handling
Map P2002, P2025, P2003, P2014 to HTTP status codes
Register filter globally using HttpAdapterHost
Add environment checks for dev vs production errors
Write integration tests verifying status codes
Log errors to monitoring systems in production
Consider nestjs-prisma package for maintained mappings
Handle connection errors separately from query errors
Document handled error codes
Review error handling during code reviews
Proper global exception filters separate good APIs from great ones. Users get helpful feedback, developers get useful logs, monitoring systems track meaningful metrics. The initial setup takes an hour. The time saved debugging production issues pays back within days.



Comments