Next.js에서 Prisma를 이용해 REST API를 만들어 보자 - 1

이준혁·2022년 5월 5일
8

next-tutorials

목록 보기
2/7
post-thumbnail

이 글은 Next.js와 Prisma를 이용해 REST API를 만드는 법에 대해 설명합니다.
다음 지식들을 안다면 문제없이 읽을 수 있습니다.

  • TypeScript
  • DB, SQL 기초 지식
  • Node.js
  • React 기초 문법
  • Next.js 기초 문법
  • Prisma
  • REST API
  • Promise

Prisma Client

prisma를 코드 상에서 사용하기 위해서는 prisma에서 제공하는 prisma client 라는 도구를 사용해야 합니다.

❗ 보안 등의 이유로 브라우저는 prisma client를 실행할 수 없게 되어 있습니다!
여기서는 api route를 제공하는 /api 폴더 안에서만 prisma client를 사용할 것입니다.

자세한 내용은 prisma client 공식 문서를 참고해 주시면 됩니다.

모델 만들기

prsima client를 사용하기 위해서는 모델이 정의된 스키마 파일이 필요합니다. 스키마 파일을 다음과 같이 작성합니다.

generator client {
  provider        = "prisma-client-js"
}

datasource db {
  provider        = "mysql"
  url             = env("DATABASE_URL")
}

model Users {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  username  String
  shops     Shops[]
}

model Shops {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  users     Users    @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId    Int
  name      String
  location  String?
}

Users 모델은 각 user의 정보를 담는 모델이고, 여러 개의 shop을 가질 수 있습니다. 반면 Shops 모델은 각 shop의 정보를 담고, 한 명의 user 에게 소유됩니다.

스키마를 바꾼 후에는 항상 npx prisma db push를 통해 연결된 데이터베이스의 스키마를 변경해 줘야 합니다.

설치

npm install @prisma/client 혹은 yarn add @prisma/client 를 통해 설치하시면 됩니다.

client 만들기

prisma client를 코드 내에서 사용하기 위해 인스턴스로 만들어야 합니다. 이를 위해 /pages/apiusers.tsx 파일 맨 위에 다음 코드를 작성합니다.

import { PrismaClient } from "@prisma/client";

const client = new PrismaClient();

이제 client라는 변수를 이용해 DB 작업을 실행하면 됩니다.


REST API 설명

prisma CRUD 공식 문서를 참고해 작성되었습니다.

위 과정을 수행했다면 /pages/api/users.tsx 파일 안에 client가 만들어져 있을 것입니다. 이제 다음과 같이 빈 함수를 만들어 보겠습니다.

import { NextApiRequest, NextApiResponse } from "next";
import { PrismaClient } from "@prisma/client";

const client = new PrismaClient();

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === "GET") {
    //Read
    res.json({ok: true});
  }
  if (req.method === "POST") {
    //Create
    res.json({ok: true});
  }
  if (req.method === "PUT") {
    //Update
    res.json({ ok: true });
  }
  if (req.method === "DELETE") {
    //Delete
    res.json({ ok: true });
  }
}

/pages/api 폴더 안의 파일에서는 하나의 함수를 defalutexport 해야합니다. 여기서는 handler라는 함수가 그 역할을 하고 있고, 다음과 같은 기능을 수행할 것입니다.

  1. 응답과 요청을 인자로 받음. 각각 NextApiRequest, NextApiResponse 타입
  2. 각 요청의 method에 따라 다른 동작을 하고 응답을 보냄.
    • GET 요청의 경우 데이터를 Read
    • POST 요청의 경우 데이터를 Create
    • PUT 요청의 경우 데이터를 Update
    • DELETE 요청의 경우 데이터를 Delete

이렇게 네 가지의 요청 메소드를 받고 처리함으로 인해 REST API를 만들게 됩니다.

Read

if (req.method === "GET") {
  const allUsers = await client.users.findMany({
    where: {
      username: "Demian",
    },
  });
  const firstUsers = await client.users.findFirst({
    where: {
      username: "Demian",
    },
  });
  const uniqUsers = await client.users.findUnique({
    where: {
      id: 1,
    },
  });

위 코드는 각각 여러 방식으로 원하는 데이터를 찾는 방법입니다. 주의할 것은 client로 수행하는 함수들은 Promise를 반환하기 때문에 await을 이용해 동기 방식으로 처리해 줍니다.

findMany

findMany 함수는 where 속성 에 포함된 조건을 만족하는 모든 유저를 반환합니다. 지금 같은 경우에는 username이 Demian인 모든 user를 반환할 것입니다. 만약 조건이 아무것도 없다면, 모든 user를 반환할 것입니다.

findFirst

findFirst 함수는 findMany 함수와 비슷하지만, 조건을 만족하는 첫 유저만 반환합니다.

findUnique

findUnique 함수는 위 두 함수와 달리 username에 대한 조건을 부여할 수 없습니다. 이 함수는 조건으로 무조건 unique 속성을 가진 column 의 조건만 요구하기 때문이죠. 여기서는 id가 1인 유저를 찾아 반환할 것입니다.

Include & Select

만약 users 테이블의 정보를 가져오면서, 이 각 사용자가 소유한 가게들의 이름도 같이 가져오고 싶다면 어떻게 하면 될까요? 다음 코드를 보시죠.

const usersWshops = await client.users.findMany({
  include: {
    shops: true
  },
});

const usersWshopsName = await client.users.findMany({
  include: {
    shops: {
      select: {
        name: true,
      },
    },
  },
});

조금 복잡하지만 살펴봅시다. 첫 번째 함수처럼 include를 사용하면, 연결된 다른 모델의 정보도 포함해서 가져올 수 있습니다. 여기서 shopname만 가져오고 싶다면, 두 번째 함수와 같이 select를 이용하면 됩니다.

Create

if (req.method === "POST") {
  const newUser = await client.users.create({
    data: {
      username: "Frau Eva",
    },
  });
  const newUsers = await client.users.createMany({
    data: [{ username: "Emil Sinclair" }, { username: "Abraxas" }],
  });

위 코드는 새 사용자를 만드는 두 가지 방법입니다. create 함수와 createMany 함수가 있습니다.

create

하나의 user 데이터를 data 속성에 주면 됩니다. 다만 주의해야할 점은, username은 반드시 필요한 속성이기 때문에 반드시 그 값을 줘야 합니다.

createMany

user 데이터 배열을 주는 것 외에는 create 함수와 완전히 같습니다.

nested create - users

만약 새 user를 생성할 때, shop도 같이 생성하고 싶다면, 다음과 같이 작성하면 됩니다.

const newUser = await client.users.create({
  data: {
    username: "Frau Eva",
    shops: {
      create: {
        name: "Bella",
        location: "Hanyang",
      },
    },
  },
});

위와 같이 shops 속성에 create 속성을 주고, 그 내부에 또 shop의 정보를 주면 user가 생성되면서 shop이 동시에 생성됩니다. create 대신 createMany 속성을 줘서 여러 shop을 생성하는것 역시 가능합니다.

nested create - shops

반면 새 shop을 생성하는 것은 조금 다른 이야기입니다. shop을 생성하기 위해서는 user반드시 필요한데, 이 user와 연결하기 위해서는 세 가지 방법이 있습니다.

  1. 새 user를 만들기 (create)
  2. 기존 user에게 연결 (connect)
  3. 특정 조건으로 user를 검색 후, 해당하는 user가 있으면 연결하고, 없으면 새로 만듦.
    (connectOrCreate)
const newShopWCreate = await client.shops.create({
  data: {
    name: "IU",
    users: {
      create: {
        username: "jieun",
      },
    },
  },
});

const newShopWConnect = await client.shops.create({
  data: {
    name: "IU",
    users: {
      connect: {
        id: 1,
      },
    },
  },
});

const newShopWCrOrCon = await client.shops.create({
  data: {
    name: "IU",
    users: {
      connectOrCreate: {
        where: {
          id: 1,
        },
        create: {
          username: "jieun",
        },
      },
    },
  },
});
  1. user를 만들기 (create)

    • 이 경우는 user를 꼭 만들기 위해 필요한 username을 제공하면 됩니다.
  2. 기존 user에게 연결 (connect)

    • 이 경우는 users 테이블의 unique 속성을 가진 column의 값을 제공해 줌으로 인해 유일한 user와 연결해 줍니다.
  3. 특정 조건으로 user를 검색 후, 해당하는 user가 있으면 연결하고, 없으면 새로 만듦.(connectOrCreate)

    • 1번과 2번을 합쳐 놓은 것으로, where 속성에서는 해당 조건을 만족하는 지 검색 후, 있다면 연결하고, 없다면 create 속성 내부의 user를 만들어 주면 됩니다.

Update

if (req.method === "PUT") {
  const updateUser = await client.users.update({
    where: {
      id: 1,
    },
    data: {
      username: "Franz",
    },
  });
  const updateUsers = await client.users.updateMany({
    where: {
      username: {
        contains: "demian"
      },
      id: {
        lte: 3,
      },
    },
    data: {
      username: "Alfons",
    },
  });

update 역시 create 혹은 read와 크게 다르지 않습니다. update 혹은 updateMany 함수를 사용할 수 있습니다.

update

where 속성을 이용해 특정 user를 찾고, data 속성에 값을 넣어 해당 값으로 업데이트를 해 줍니다. 위의 코드는 1번 id를 가지고 있는 userusername을 Franz로 변경하는 코드입니다.

updateMany

update 함수와 유사하나, where조건에 특정 user가 아닌 조건을 만족하는 모든 user들을 찾을 수 있도록 조건을 설정할 수 있습니다. unique 속성을 가지지 않은 column들에 대해서도 사용할 수 있습니다. 또한 contains등을 사용한 여러 조건을 부여 할 수도 있습니다.

위의 코드는 3번 이하의 id를 가지고, usernamedemian을 포함한 모든 user의 이름을 alfons로 바꾸는 코드입니다.

  • lt : less than
  • lte : less than or equal
  • gt : greater than
  • gte : greater than or equal

Delete

if (req.method === "DELETE") {
  const deleteUser = await client.users.delete({
    where: {
      id: 3,
    },
  });
  const deleteUsers = await client.users.deleteMany({
    where: {
      username: {
        contains: "Alfons",
      },
    },
  });

delete 역시 update 혹은 find와 유사합니다. delete 함수로 특정 user를, deleteMany 함수로 특정 조건들을 만족하는 user들을 삭제할 수 있습니다.


마치며

이 포스트에서는 Next.js에서 prisma를 이용해 REST API를 만드는 법에 대해 알아 봤습니다. 하지만 직접 사용자로부터 받은 데이터로 작업을 진행하진 않았습니다.

다음 포스트에서는 프론트엔드에서 직접 api에 요청을 보내고 응답을 받는 방법을 알아보겠습니다. 코드 원본은 여기를 참고해 주시면 됩니다.

참고자료

  1. Prisma client
  2. Prisma client CRUD
  3. Next JS 강의
profile
만들고 개선하는 것을 좋아하는 개발자

0개의 댓글