본문 바로가기
개발

[Prisma] create api 사용시 안전하게 데이터 생성하기

by 박연호의 개발 블로그 2022. 11. 26.
model Post {
  id        Int    @id @default(autoincrement())
  content   String
  authorId  Int
  author    User   @relation(fields: [authorId], references: [id])

  @@index([authorId], map: "Post_authorId_fkey")
}

model User {
  id     Int    @id @default(autoincrement())
  name   String
  posts  Post[]
}

 

위의 스키마를 기반으로 아래의 코드를 작성한다고 생각해 보겠습니다.

   const post = await prisma.post.create({
      data: {...},
    });

 

여기서 prisma는 prisma client api에 대한 모든 타입을 제공하고 있으며, 위의 Post를 create할 때 데이터 타입은 다음과 같습니다.

 /**
   * Post create
   */
  export type PostCreateArgs = {
    /**
     * Select specific fields to fetch from the Post
     * 
    **/
    select?: PostSelect | null
    /**
     * Choose, which related nodes to fetch as well.
     * 
    **/
    include?: PostInclude | null
    /**
     * The data needed to create a Post.
     * 
    **/
    data: XOR<PostCreateInput, PostUncheckedCreateInput>
  }

재밌는 점은 data의 타입이 XOR<PostCreateInput, PostUncheckedCreateInput> 라는 것인데요, data를 생성할 때 2개의 타입중 하나의 타입으로 생성이 가능합니다. 

 

Prisma 팀에서는 위처럼 2가지의 타입을 나누어 상황에 맞게 적절히 사용하도록 가이드 하고 있습니다.

 

PostUncheckedCreateInput

  export type PostUncheckedCreateInput = {
    id?: number
    content: string
    authorId: number
  }

게시글에는 작성자가 존재합니다. 여기서 authorId에 작성자의 pk를 넣어주면 됩니다. 간단합니다. 근데 생각 해보면, authoidId가 개발자의 실수로 잘못 들어갈 수도 있습니다. 또는 해당 pk를 가지는 유저가 존재하지 않을 수도 있습니다. 충분히 그럴 가능성이 있습니다.

 

이렇게 authorId에 들어가는 값을 좀 더 안전하게 보장하는 방법이  PostCreateInput를 사용하는 방법입니다.

PostCreateInput

  export type PostCreateInput = {
    content: string
    author: UserCreateNestedOneWithoutPostsInput
  }
  
    export type UserCreateNestedOneWithoutPostsInput = {
    create?: XOR<UserCreateWithoutPostsInput, UserUncheckedCreateWithoutPostsInput>
    connectOrCreate?: UserCreateOrConnectWithoutPostsInput
    connect?: UserWhereUniqueInput
  }

PostCreateInput는 PostUncheckedCreateInput보다 좀 더 보수적으로 동작합니다. authorId에 값을 바로 넣는 대신, 유저를 만들어서 넣거나, authorId가 없는 경우 만들어서 넣거나와 같은 방법으로 좀 더 안전한 방법을 사용하고 있습니다.

 

게시글의 작성자의 데이터 타입은 UserCreateNestedOneWithoutPostsInput이며 3가지 옵션을 제공하고 있습니다.

 

create

게시글을 만들 때 유저를 같이 만들고 유저의 pk값을 자동으로 게시글의 authorId로 넣음

 const post = await prisma.post.create({
      data: {
        author: {
          create: {
            name: "user1",
          },
        },
        content: "content1",
      },
    });

 

connectOrCreate

게시글의 authorId를 주는데, 만약 authorId가 없으면 유저를 생성해서 유저의 pk값을 자동으로 게시글의 authorId로 넣음

 const post = await prisma.post.create({
      data: {
        author: {
          connectOrCreate: {
            where: {
              id: 100,
            },
            create: {
              name: "user1",
            },
          },
        },
        content: "content1",
      },
    });

 

connect

authorId를 그대로 넣으며, PostUncheckedCreateInput와 비슷함

    const post = await prisma.post.create({
      data: {
        author: {
          connect: {
            id: 100,
          },
        },
        content: "content1",
      },
    });

 

PostCreateInput와 PostUncheckedCreateInput를 사용했을때 반환하는 에러코드와 메시지도 다릅니다. PostCreateInput가 좀 더 상세히 메시지를 보여 주는 것 같네요.

PostCreateInput
 code: 'P2025',
 clientVersion: '4.3.0',
 meta: {
   cause: "No 'User' record(s) (needed to inline the relation on 'Post' record(s)) was found for a nested connect on one-to-many relation 'PostToUser'."
 }

PostUncheckedCreateInput
 code: 'P2003',
 clientVersion: '4.3.0',
 meta: { field_name: 'authorId' }

 

prisma 공식문서에서는 위의 에러코드를 아래처럼 정의하고 있습니다.