이 글은 Next.js와 Prisma를 이용해 REST API를 만드는 법에 대해 설명합니다.
다음 지식들을 안다면 문제없이 읽을 수 있습니다.
- TypeScript
- DB, SQL 기초 지식
- Node.js
- React 기초 문법
- Next.js 기초 문법
- Prisma
- REST API
- Promise
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
를 통해 설치하시면 됩니다.
prisma client
를 코드 내에서 사용하기 위해 인스턴스로 만들어야 합니다. 이를 위해 /pages/api
의 users.tsx
파일 맨 위에 다음 코드를 작성합니다.
import { PrismaClient } from "@prisma/client";
const client = new PrismaClient();
이제 client
라는 변수를 이용해 DB
작업을 실행하면 됩니다.
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
폴더 안의 파일에서는 하나의 함수를 defalut
로 export
해야합니다. 여기서는 handler
라는 함수가 그 역할을 하고 있고, 다음과 같은 기능을 수행할 것입니다.
- 응답과 요청을 인자로 받음. 각각
NextApiRequest
,NextApiResponse
타입- 각 요청의
method
에 따라 다른 동작을 하고 응답을 보냄.
GET
요청의 경우 데이터를Read
POST
요청의 경우 데이터를Create
PUT
요청의 경우 데이터를Update
DELETE
요청의 경우 데이터를Delete
이렇게 네 가지의 요청 메소드를 받고 처리함으로 인해 REST API
를 만들게 됩니다.
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
함수는 where
속성 에 포함된 조건을 만족하는 모든 유저를 반환합니다. 지금 같은 경우에는 username
이 Demian인 모든 user
를 반환할 것입니다. 만약 조건이 아무것도 없다면, 모든 user를 반환할 것입니다.
findFirst
함수는 findMany
함수와 비슷하지만, 조건을 만족하는 첫 유저만 반환합니다.
findUnique
함수는 위 두 함수와 달리 username
에 대한 조건을 부여할 수 없습니다. 이 함수는 조건으로 무조건 unique 속성을 가진 column
의 조건만 요구하기 때문이죠. 여기서는 id
가 1인 유저를 찾아 반환할 것입니다.
만약 users
테이블의 정보를 가져오면서, 이 각 사용자가 소유한 가게들의 이름도 같이 가져오고 싶다면 어떻게 하면 될까요? 다음 코드를 보시죠.
const usersWshops = await client.users.findMany({
include: {
shops: true
},
});
const usersWshopsName = await client.users.findMany({
include: {
shops: {
select: {
name: true,
},
},
},
});
조금 복잡하지만 살펴봅시다. 첫 번째 함수처럼 include
를 사용하면, 연결된 다른 모델의 정보도 포함해서 가져올 수 있습니다. 여기서 shop
의 name
만 가져오고 싶다면, 두 번째 함수와 같이 select
를 이용하면 됩니다.
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
함수가 있습니다.
하나의 user
데이터를 data
속성에 주면 됩니다. 다만 주의해야할 점은, username
은 반드시 필요한 속성이기 때문에 반드시 그 값을 줘야 합니다.
user
데이터 배열을 주는 것 외에는 create
함수와 완전히 같습니다.
만약 새 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
을 생성하는것 역시 가능합니다.
반면 새 shop을 생성하는 것은 조금 다른 이야기입니다. shop
을 생성하기 위해서는 user
가 반드시 필요한데, 이 user
와 연결하기 위해서는 세 가지 방법이 있습니다.
- 새 user를 만들기 (create)
- 기존 user에게 연결 (connect)
- 특정 조건으로 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",
},
},
},
},
});
새 user
를 만들기 (create)
user
를 꼭 만들기 위해 필요한 username
을 제공하면 됩니다.기존 user
에게 연결 (connect)
users
테이블의 unique
속성을 가진 column
의 값을 제공해 줌으로 인해 유일한 user
와 연결해 줍니다.특정 조건으로 user를 검색 후, 해당하는 user가 있으면 연결하고, 없으면 새로 만듦.(connectOrCreate)
where
속성에서는 해당 조건을 만족하는 지 검색 후, 있다면 연결하고, 없다면 create
속성 내부의 user
를 만들어 주면 됩니다.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
함수를 사용할 수 있습니다.
where
속성을 이용해 특정 user
를 찾고, data
속성에 값을 넣어 해당 값으로 업데이트를 해 줍니다. 위의 코드는 1번 id
를 가지고 있는 user
의 username
을 Franz로 변경하는 코드입니다.
update
함수와 유사하나, where
조건에 특정 user
가 아닌 조건을 만족하는 모든 user
들을 찾을 수 있도록 조건을 설정할 수 있습니다. unique
속성을 가지지 않은 column
들에 대해서도 사용할 수 있습니다. 또한 contains
등을 사용한 여러 조건을 부여 할 수도 있습니다.
위의 코드는 3번 이하의 id
를 가지고, username
에 demian
을 포함한 모든 user
의 이름을 alfons
로 바꾸는 코드입니다.
- lt : less than
- lte : less than or equal
- gt : greater than
- gte : greater than or equal
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
에 요청을 보내고 응답을 받는 방법을 알아보겠습니다. 코드 원본은 여기를 참고해 주시면 됩니다.