왜 내 Prisma P2002가 NestJS에서 try/catch를 피했는가

발행: (2026년 4월 16일 AM 01:58 GMT+9)
4 분 소요
원문: Dev.to

Source: Dev.to

배경

나는 RunHop을 공개적으로 만들고 있다. 이는 달리기 레이스를 위한 소셜 + 이벤트 플랫폼이다. 오늘은 Reactions 모듈, 즉 게시물에 대한 좋아요 기능을 작업했다.

모듈 자체는 간단했다:

  • POST /posts/:id/likes
  • DELETE /posts/:id/likes
  • Prisma의 postLike 모델과 통신하는 ReactionService
  • 정상 경로와 중복 좋아요 경로에 대한 단위 테스트와 e2e 커버리지

버그

중복‑좋아요 흐름이 실패했다. src/domain/social/reaction/reaction.service.ts 파일에 다음과 같이 작성했다:

async like(postId: string, userId: string) {
  const post = await this.postService.findById(postId);

  if (!post) throw new NotFoundException('Post not found');

  try {
    return this.prisma.postLike.create({
      data: { postId, userId },
    });
  } catch (error) {
    if (
      error instanceof PrismaClientKnownRequestError &&
      error.code === 'P2002'
    ) {
      throw new ConflictException('Duplicate like');
    }

    throw error;
  }
}

겉보기엔 괜찮아 보인다. try/catch가 있고, Prisma가 고유 제약 위반 시 P2002를 던지며, 이를 ConflictException으로 매핑한다. 그런데도 단위 테스트는 실패했다.

왜 실패했는가

  • postLike.create()프라미스를 반환한다.
  • 그 프라미스가 거부(reject)될 때, 거부는 비동기적으로 발생한다.
  • 프라미스를 try 블록 안에서 바로 반환했기 때문에, 함수는 거부가 발생하기 전에 종료되고, catch 블록은 Prisma 오류를 전혀 보지 못한다.

해결 방법

try 블록 안에서 프라미스를 await한다:

async like(postId: string, userId: string) {
  const post = await this.postService.findById(postId);

  if (!post) throw new NotFoundException('Post not found');

  try {
    return await this.prisma.postLike.create({
      data: { postId, userId },
    });
  } catch (error) {
    if (
      error instanceof PrismaClientKnownRequestError &&
      error.code === 'P2002'
    ) {
      throw new ConflictException('Duplicate like');
    }

    throw error;
  }
}

return await를 사용하면 거부된 프라미스가 try/catch 경계 안에 머무르게 되어, 오류를 잡아 프레임워크 예외로 변환할 수 있다.

추가 개선 사항

  • ownershipCheck(likeId, userId) 가드를 추가해 오직 소유자만 좋아요를 삭제할 수 있게 하고, Prisma P2025NotFoundException으로 매핑했다.
  • 설계 불일치 사항을 지적: 삭제 라우트는 DELETE /posts/:id/likes인데, 현재 :id게시물 ID가 아니라 좋아요 ID를 의미한다. 동작은 하지만, 생성과 삭제 사이에 라우트 형태가 두 가지 의미를 갖는다.

교훈

try/catch는 내부에 남아 있는 코드만 잡는다. 비동기 데이터베이스 오류를 프레임워크 예외로 변환할 때는 **await**를 사용해 거부된 프라미스를 try 블록 안에서 처리해야 한다.

0 조회
Back to Blog

관련 글

더 보기 »

TypeOrm 유닛 오브 워크

TypeOrm Unit Of Work의 커버 이미지 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s...