2 min read

Nest.js | Prisma Exception Filter

Nest.js | Prisma Exception Filter

In Nest.js, you can create an exception filter to handle specific exceptions that may occur in your application. To create an exception filter, you can use the @UseFilters() decorator and pass in an instance of a class that implements the ExceptionFilter interface. The class should have a catch() method that takes in an instance of the exception that was thrown and a ExecutionContext object.

For example, you can create a class HttpExceptionFilter that implements the ExceptionFilter interface:

import { ExceptionFilter, Catch, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, context: ExecutionContext) {
    // Handle the exception here
  }
}

You can then apply the filter globally or on specific controllers or routes using the @UseFilters() decorator:

@UseFilters(new HttpExceptionFilter())
@Controller()
export class MyController {
  // ...
}

or:

@UseFilters(new HttpExceptionFilter())
@Get()
async findAll() {
  // ...
}

You can also catch a different type of exception by doing

@Catch(MyException)
export class MyExceptionFilter implements ExceptionFilter {
  catch(exception: MyException, context: ExecutionContext) {
    // Handle the exception here
  }
}

In Nest.js, you can create an exception filter to handle specific exceptions that may occur when using Prisma ORM. To create an exception filter for Prisma, you can use the @UseFilters() decorator and pass in an instance of a class that implements the ExceptionFilter interface. The class should have a catch() method that takes in an instance of the exception that was thrown and a ExecutionContext object.

For example, you can create a class PrismaExceptionFilter that implements the ExceptionFilter interface:

import {
  ArgumentsHost,
  Catch,
  HttpException,
  HttpServer,
  HttpStatus,
} from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { Prisma } from '@prisma/client';

export type ErrorCodesStatusMapping = {
  [key: string]: number;
};

/**
 * {@link PrismaClientExceptionFilter}
 * catches {@link Prisma.PrismaClientKnownRequestError}
 * and {@link Prisma.NotFoundError} exceptions.
 */
@Catch(Prisma?.PrismaClientKnownRequestError, Prisma?.NotFoundError)
export class PrismaClientExceptionFilter extends BaseExceptionFilter {
  /**
   * default error codes mapping
   *
   * Error codes definition for Prisma Client (Query Engine)
   * @see https://www.prisma.io/docs/reference/api-reference/error-reference#prisma-client-query-engine
   */
  private errorCodesStatusMapping: ErrorCodesStatusMapping = {
    P2000: HttpStatus.BAD_REQUEST,
    P2002: HttpStatus.CONFLICT,
    P2025: HttpStatus.NOT_FOUND,
  };

  /**
   * @param applicationRef
   * @param errorCodesStatusMapping
   */
  constructor(
    applicationRef?: HttpServer,
    errorCodesStatusMapping?: ErrorCodesStatusMapping,
  ) {
    super(applicationRef);

    if (errorCodesStatusMapping) {
      this.errorCodesStatusMapping = Object.assign(
        this.errorCodesStatusMapping,
        errorCodesStatusMapping,
      );
    }
  }

  /**
   * @param exception
   * @param host
   * @returns
   */
  catch(
    exception: Prisma.PrismaClientKnownRequestError | Prisma.NotFoundError | any,
    host: ArgumentsHost,
  ) {
    if (exception instanceof Prisma.PrismaClientKnownRequestError) {
      return this.catchClientKnownRequestError(exception, host);
    }
    if (exception instanceof Prisma.NotFoundError) {
      return this.catchNotFoundError(exception, host);
    }
  }

  private catchClientKnownRequestError(
    exception: Prisma.PrismaClientKnownRequestError,
    host: ArgumentsHost,
  ) {
    const statusCode = this.errorCodesStatusMapping[exception.code];
    const message = `[${exception.code}]: ${this.exceptionShortMessage(exception.message)}`;

    if (!Object.keys(this.errorCodesStatusMapping).includes(exception.code)) {
      return super.catch(exception, host);
    }

    super.catch(new HttpException({ statusCode, message }, statusCode), host);
  }

  private catchNotFoundError(
    { message }: Prisma.NotFoundError,
    host: ArgumentsHost,
  ) {
    const statusCode = HttpStatus.NOT_FOUND;

    super.catch(new HttpException({ statusCode, message }, statusCode), host);
  }

  private exceptionShortMessage(message: string): string {
    const shortMessage = message.substring(message.indexOf('→'));

    return shortMessage
      .substring(shortMessage.indexOf('\n'))
      .replace(/\n/g, '')
      .trim();
  }

Change main.js:

{
    const { httpAdapter } = app.get(HttpAdapterHost);

    app.useGlobalFilters(
      new AllExceptionsFilter(),
      new PrismaClientExceptionFilter(httpAdapter),
    );
  }

or:

{
  const { httpAdapter } = app.get(HttpAdapterHost);
  
  app.useGlobalFilters(new PrismaClientExceptionFilter(httpAdapter, {
      P2022: HttpStatus.BAD_REQUEST,
  }));
}

If you have any questions or feedback about this article, feel free to leave a comment.