Skip to content

[황영일] 스프린트 미션 6#80

Open
youngsecondbrain-ai wants to merge 6 commits into
codeit-sprint-fullstack:react-황영일from
youngsecondbrain-ai:react-prisma
Open

[황영일] 스프린트 미션 6#80
youngsecondbrain-ai wants to merge 6 commits into
codeit-sprint-fullstack:react-황영일from
youngsecondbrain-ai:react-prisma

Conversation

@youngsecondbrain-ai

Copy link
Copy Markdown
Collaborator

요구사항

기본 요구사항

중고마켓

  • mongoDB에서 PostgreSQL을 사용하도록 코드를 마이그레이션 해주세요.

공통

  • PostgreSQL를 이용해 주세요.
  • 데이터 모델 간의 관계를 고려하여 onDelete를 설정해 주세요.
  • 데이터베이스 시딩 코드를 작성해 주세요.
  • 각 API에 적절한 에러 처리를 해 주세요.
  • 각 API 응답에 적절한 상태 코드를 리턴하도록 해 주세요.

자유게시판

  • Article 스키마를 작성해 주세요.
  • id, title, content, createdAt, updatedAt 필드를 가집니다.
  • 게시글 등록 API를 만들어 주세요.
  • title, content를 입력해 게시글을 등록합니다.
  • 게시글 조회 API를 만들어 주세요.
  • id, title, content, createdAt를 조회합니다.
  • 게시글 수정 API를 만들어 주세요.
  • 게시글 삭제 API를 만들어 주세요.
  • 게시글 목록 조회 API를 만들어 주세요.
  • id, title, content, createdAt를 조회합니다.
  • offset 방식의 페이지네이션 기능을 포함해 주세요.
  • 최신순(recent)으로 정렬할 수 있습니다.
  • title, content에 포함된 단어로 검색할 수 있습니다.

댓글

  • 댓글 등록 API를 만들어 주세요.
  • content를 입력하여 댓글을 등록합니다.
  • 중고마켓, 자유게시판 댓글 등록 API를 따로 만들어 주세요.
  • 댓글 수정 API를 만들어 주세요.
  • PATCH 메서드를 사용해 주세요.
  • 댓글 삭제 API를 만들어 주세요.
  • 댓글 목록 조회 API를 만들어 주세요.
  • id, content, createdAt 를 조회합니다.
  • cursor 방식의 페이지네이션 기능을 포함해 주세요.
  • 중고마켓, 자유게시판 댓글 목록 조회 API를 따로 만들어 주세요.

@youngsecondbrain-ai youngsecondbrain-ai changed the title React prisma [황영일] 스프린트 미션 6 Jul 6, 2026

@devbini devbini left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 고생 많으셨습니다!

이번 PR은 기존 프론트엔드에 Express 서버와 Prisma, PostgreSQL 기반의 데이터 모델을 붙여보는 작업인데.,.. 서비스 안의 데이터가 어떻게 연결되는지 고민한 흔적이 보인 것 같아요.

특히 댓글이 게시글과 상품 양쪽에 연결될 수 있다는 점을 스키마에 반영해보려고 한 부분이나, 사용자 삭제 시 SetNull이나 Cascade처럼 관계별 삭제 전략도 잡아간 부분도 좋았습니다.

requests.http로 API 호출 예시를 남겨둔 것도 좋았습니다. 이런 파일은 본인 테스트에도 좋고, 다음 사람이 서버를 확인할 때도 꽤 도움이 되거든요.

다만 아직 프론트와 백엔드가 아직 하나의 흐름으로 딱 맞지 않는다는 느낌이 조금 있어요.
전체적으로 로컬에서 앱을 실행했을 때 한 번에 검증하기 어려워 보였습니다. 이 부분은 기능을 더 추가하기 전에 먼저 맞춰두면 이후 디버깅이 훨씬 쉬워질 것 같습니다.

또... 공통 에러 핸들러처럼 새로 배운 도구를 적극적으로 써보려는 시도도 좋았지만 다만 이런 도구들은 입력값이 조금만 어긋나도 바로 500으로 이어질 수 있어서, userId나 페이지네이션 값처럼 외부에서 들어오는 값은 한 번 더 검증하는 습관을 들이면 훨씬 안정적인 코드가 될 것 같아요.

앞으로는 프론트 API 주소처럼 실행 흐름에 직접 영향을 주는 부분부터 정리해보면 완성도가 많이 올라갈 것 같습니다.
너무나 고생 많으셨고, 파이팅입니다! 👍

Comment thread backend/app.js
Comment on lines +127 to +201
// // PRODUCTS //
// //GET list
// app.get("/products", async (req, res) => {
// try {
// const sort = req.query.sort;
// const page = Number(req.query.page) || 1;
// const limit = Number(req.query.count) || 10;

// const skipIndex = (page - 1) * limit;

// const sortOption = { _id: sort === "recent" ? "desc" : "asc" };

// const totalProducts = await Product.countDocuments();
// const totalPages = Math.ceil(totalProducts / limit);

// const products = await Product.find()
// .sort(sortOption)
// .skip(skipIndex)
// .limit(limit);

// res.send({
// products: products,
// currentPage: page,
// totalPages: totalPages,
// });
// } catch (e) {
// res.status(500).send({ message: "서버 오류가 발생했습니다" });
// }
// });

// //GET id
// app.get("/products/:id", async (req, res) => {
// const product = await Product.findById(req.params.id);
// if (product) {
// res.send(product);
// } else {
// res.status(404).send({ message: "Cannot find given id." });
// }
// });

// //POST
// app.post("/products", async (req, res) => {
// const newProduct = await Product.create(req.body);
// res.status(201).send(newProduct);
// });

// //PATCH
// app.patch("/products/:id", async (req, res) => {
// const id = req.params.id;
// const product = await Product.findById(id);

// if (product) {
// Object.keys(req.body).forEach((key) => {
// product[key] = req.body[key];
// });

// // 이것 작성해야 mongoDB에 저장됨. 필수!
// await product.save();

// res.send(product);
// } else {
// res.status(404).send({ message: "Cannot find given id." });
// }
// });

// //DELETE
// app.delete("/products/:id", async (req, res) => {
// const id = req.params.id;
// const product = await Product.findByIdAndDelete(id);
// if (product) {
// res.sendStatus(204);
// } else {
// res.status(404).send({ message: "Cannot find given id. " });
// }
// });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Check Point

혹시 어떤 이유가 있는 걸까요?
현재 상태만으로는 상품 목록 조회부터 등록 흐름까지 확인하는 데 어려움이 있는 것 같아요.
다른 곳에서 제대로 구현을 이미 했다면, 사용하지 않는 코드는 바로바로 제거해서 실제로 동작하는 API들만 남겨주시는 게 좋습니다 🤗

Comment thread frontend/src/Items.jsx
Comment on lines +100 to +115
<div className='pagination'>
<button
onClick={() => setPage(page - 1)}>
{'<'}
</button>

{pageNumbers.map((pageNumber) => (
<button key={pageNumber}
onClick={() => setPage(pageNumber)}>{pageNumber}
</button>
))}

<button
onClick={() => setPage(page + 1)}>
{'>'}
</button>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Check Point

1페이지에서 이전 버튼을 누르면 page가 0이 되고, 마지막 페이지에서 다음 버튼을 누르면 전체 페이지보다 큰 값이 요청되는 문제가 발생할 것 같아요.

이 경우 API가 예상하지 못한 페이지 값을 받거나, 빈 데이터가 계속 렌더링될 수 있어요.

이전 버튼은 page === 1일 때 비활성화하고, 다음 버튼은 page === totalPages일 때 막아두는 예외처리에 대한 장애대응을 습관화 해 두시면 좋습니다.

Comment thread backend/app.js
app.delete("/articles/:id", async (req, res) => {
const { id } = req.params;
await prisma.article.delete({ where: { id } });
res.send(204);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Check Point

보통 상태 코드를 보낸다 라는 건 res.sendStatus 또는 res.status(204).send(...) 형태로 써 주셔야 해요.
이 경우에는 그저 "204" 라는 "string 값"을 반환하는 것으로 보여서, 원하는 대로 동작하지 않을 위험이 보이네요!

name: name,
description: description,
price: Number(price),
tags: tags,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☕ Thinking

상품 등록 요청에서 tags가 문자열 하나로 전송되고 있네요.
프론트 상태도 useState('')라서 여러 태그를 배열로 관리하기 어렵고, 서버나 API가 tags: string[] 형태를 기대한다면 문제가 생길 수 있어요.

음.. 가능하면 이렇게 "여러개를 관리해야 하는 요소"라면, 배열로 관리하는 게 더 유지보수적으로도, 코드를 개발할 때도 좋은 방법이 되기도 한답니다.
거기에 추가로 Enter로 추가하는 흐름을 만들면 UX적으로도 향상이 될 것 같아요ㅎㅎ 🤗

Comment thread backend/app.js
// ARTICLES
//
app.get("/articles", async (req, res) => {
const { offset = 0, limit = 10, order = "newest" } = req.query;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☕ Thinking

이것도 문제는 아니지만,
숫자를 바로 DB로 던지고 있는 것 같아요. 페이지네이션 관련 데이터를..

정상적인 상황에서는 문제가 없지만, 실제 환경에서는 어떤 문제가 나올지 몰라요.
예를 들어- limit 데이터가 음수로 들어간다거나, 비정상적으로 엄청 큰 데이터가 들어간다던가...

Math 라이브러리를 활용하면 아마 상/하 제한을 걸 수 있을텐데, 서버 리소스 보호를 생각하는 방법이 하나 더 있지 않을까 싶어 남겨둡니다ㅎㅎ

Comment on lines +56 to +59
article Article? @relation(fields: [articleId], references: [id], onDelete: Cascade)
articleId String?
product Product? @relation(fields: [productId], references: [id], onDelete: Cascade)
productId String?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Check Point

현재 스키마 구조를 보면,
productId와 articleId가 모두 없는 댓글도 만들 수 있고, 반대로 둘 다 있는 댓글을 만들 수 있네요.

XOR 제약을 직접 표현하기 어렵다면, 댓글 생성 라우터에서 productId와 articleId 중 하나만 들어가도록 검증을 추가하거나, ProductComment, ArticleComment와 같이 모델을 분리하는 방법이 필요 해 보여요.

Comment thread backend/app.js
const comment = await prisma.comment.create({
data: {
content: req.body.content,
user: { connect: { id: req.body.userId } },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☕ Thinking

댓글 생성 시 userId를 body에서 받아 바로 connect하는데
바로 위 코멘트처럼 스키마상 Comment.userIdoptional이라, 실제 API 관점에서는 userId가 필수인지 선택인지 애매한 것 같아요.

지금 코드에서는 userId가 없으면 Prisma 에러로 이어질 수 있으니, 필수라면 400으로 먼저 막고, 선택이라면 userId가 있을 때만 connect하는 구조를 추가하는 건 어떨까요?

Comment thread backend/app.js
return res.status(404).json({ error: `Couldn't find requested data` });
}

res.status(500).json({ message: err.message });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Check Point

음.. 이건 조금 위험해요.
요구사항에 500에 대한 응답 에러처리가 있어서 이렇게 해도 되는건지는 잘 모르겠지만..

500의 경우에는 DB 연결 내용이나 구현에 있어 치명적일 수 있는 서버 오류메시지가 사용자에게 그대로 전달되게 됩니다.

가능하면 message를 하드하게 별도로 만들거나, 에러핸들러를 따로 만들어서 관리하는 게 좋을 것 같아요.

블라인드 SQL 인젝션 등, 다양한 보안적 위험요소가 존재한답니다.

자세한 오류 로그는 서버 로그에만 남기는 게 좋아요! 🥹

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants