이전까지는 JSON server를 사용한 프로젝트만 진행했지만, 이번 리팩토링 프로젝트에서 Next-Auth를 사용하게 되면서, DB를 MongoDB로 선택하게 되었다.
Next.js API route를 사용하기 때문에 app/api/review/route.js
파일을 생성해 주었다. 메서드 명으로 구분하여 CRUD 로직을 작성하면 된다.
CREATE
먼저 리뷰를 생성하는 로직이다.
리뷰를 작성하는 폼에서, 필요한 정보를을 담아 fetch를 사용하고, body에 stringify
하여 전달해 준다. 이 때, 메서드를 POST
로 작성하여 넘겨주어야 route 측에서 제대로 처리를 해줄 수 있다.
reviewform.tsx
const onSubmit = async (data: any) => {
const reviewImage = localStorage.getItem("preview-image");
const imgRef = ref(storageService, `${session.user.id}/${Date.now()}`);
let downloadUrl;
if (reviewImage) {
const response = await uploadString(imgRef, reviewImage, "data_url");
downloadUrl = await getDownloadURL(response.ref);
}
try {
const res: any = await fetch("/api/review", {
body: JSON.stringify({
...data,
photo: downloadUrl ?? "",
hospitalId: hospitalData.id,
userId: session?.user?.id,
categories,
rate: rating,
totalAmounts: +data.totalAmounts,
createdAt: Date.now().toString(),
hospitalName: hospitalData.place_name,
hospitalAddress: hospitalData.address_name,
}),
method: "POST",
});
if (!res.ok) {
throw new Error(res?.message);
}
} catch (error) {
console.log(error);
return;
}
setReviewOpen(false);
};
다음으로는, 서버 측 로직이다. body로 넘겨준 데이터를 받아오고, 조건에 따라 에러 처리를 해 준다.
로그인이 되어 있지 않은 경우, 클라이언트 측에서 따로 처리를 해 주었지만 비어서는 안되는 값들도 서버 측에서 2차로 검증할 수 있도록 처리했다.
이외로 자동으로 전달되며, 반드시 전달받아야 하는 값인, 병원 ID와 생성일(createdAt)에 대한 검사도 진행한 뒤, 에러가 발생하지 않으면 prisma.create
를 통해 Create를 진행할 수 있다.
app/api/review/route.js
export async function POST(req) {
const {
title,
review,
totalAmounts,
rate,
photo,
hospitalId,
userId,
categories,
createdAt,
hospitalName,
hospitalAddress,
} = await req.json();
if (!userId) {
return NextResponse.json({
ok: false,
message: "로그인 후 이용해 주세요.",
});
}
if (!title || !review || !totalAmounts || !rate) {
return NextResponse.json({
ok: false,
message: "비어 있는 필드가 존재합니다.",
});
}
if (!hospitalId || !createdAt) {
return NextResponse.json({
ok: false,
message: "알 수 없는 오류가 발생했습니다.",
});
}
let post;
try {
post = await prisma.reviews.create({
data: {
title,
review,
totalAmounts,
rate,
photo:
photo === ""
? "https://firebasestorage.googleapis.com/v0/b/petpital-v2.appspot.com/o/assets%2Fno_image_info%20(1).png?alt=media&token=f5a0cb7a-47c1-4ba9-b3f4-c87795cc21d4"
: photo,
hospitalId,
userId,
categories,
createdAt,
hospitalName,
hospitalAddress,
},
});
} catch (error) {
console.log(error);
return NextResponse.json({
ok: false,
message: "알 수 없는 오류가 발생했습니다.",
});
}
return NextResponse.json(post);
}
READ
두번째로는 GET로직이다. Prisma의 기능을 통해, 하나의 값만 가져올 수도 있고, 특정 조건에 맞는 다수의 값을 가져올 수도 있다. 먼저 단일의 값을 가져오는 로직은 다음과 같다. 이번에는 유효성 검사는 생략했다.
api/auth/[...nextauth]/route.js
const user = await prisma.user.findUnique({
where: {
email: credentials.email,
},
});
위는 Prisma schema를 통해 유일한 값이어야 한다고 설정해 둔 이메일을 통해 유일한 값을 가져오는 메서드다. prisma.<name>.findUnique
메서드를 사용하면 값에 맞는 데이터만 반환해 준다.
다음은 병원 아이디에 맞는 다수의 데이터를 가져오는 메서드다.
let reviews;
try {
reviews = await prisma.reviews.findMany({
where: {
hospitalId,
},
include: {
user: true,
},
});
} catch (error) {
console.log(error);
}
병원에 따라 작성된 리뷰를 띄워주는 로직이다. findMany
를 사용하면 hospitalId
에 맞는 값들을 모두 가져올 수 있다. 여기에 만일 필드에 포함된 필드를 가져오고 싶으면 include
를 사용할 수 있다. 리뷰를 작성할 때, user의 데이터를 함께 저장하고, user의 프로필 사진과 닉네임 등을 가져올 수 있도록 하기 위해, 작성 시에 user데이터에 접근할 수 있도록 처리했다.
findUnique
와 findMany
등의 메서드를 통해 GET 로직을 작성할 수 있다. 그러나, GET은 body를 포함할 수 없기 때문에, 클라이언트 측에서 param
를 통해 필요한 값을 전달하고,
const res = await fetch(`/api/review?id=${id}`, {
method: "GET",
});
서버 측에서 다음과 같이 받아오는 방식으로 처리했다.
route.ts
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
UPDATE
업데이트 로직은 다음과 같이 updateMany
메서드를 사용하여 작성할 수 있다. 업데이트 시에는, 클라이언트 측에서 userId가 동일한 경우에만 UPDATE를 진행할 수 있도록 처리해 두었지만, 추가로 서버 측에서도 현재 수정을 진행하는 user의 id와, 작성자의 id가 같은지 추가 검증 과정을 추가했다.
const user = await prisma.user.updateMany({
where: {
id: userId,
},
data: {
name,
image,
},
});
DELETE
삭제하는 로직은, 먼저 리뷰를 작성한 리뷰가 존재하는지, 리뷰를 작성한 user의 id와 삭제하려는 user의 id가 동일한지 검증하는 과정 후에 delete
메서드를 사용하여 삭제를 진행할 수 있다.
export async function DELETE(req) {
const { reviewId, userId } = await req.json();
// 리뷰 아이디 기반으로 리뷰 가져오기
const exist = await prisma.reviews.findUnique({
where: {
id: reviewId,
},
});
// 리뷰가 존재하는지 확인
if (!exist) {
return NextResponse.json({
ok: false,
message: "이미 삭제된 리뷰입니다.",
});
}
// 리뷰에 저장된 userId랑 작성자 아이디랑 같은지 확인
if (exist.userId !== userId) {
return NextResponse.json({
ok: false,
message: "본인이 작성한 리뷰만 삭제할 수 있습니다.",
});
}
// 같으면 삭제
try {
await prisma.reviews.delete({
where: {
id: reviewId,
},
});
} catch (error) {
console.log(error);
}
return NextResponse.json(null);
}
Prisma와 MongoDB는 이번 프로젝트에서 처음 사용해 보지만, JSON server를 사용한 것과 큰 차이점이 존재하지 않아, 그리 큰 어려움을 겪지는 않았다. 그리고 MongoDB를 사용할 경우, 필드끼리 연결이 가능하고, 다양한 메서드를 제공한다는 점에서 더욱 편리하다고 느꼈다.