[백은결] sprint6#18
Hidden character warning
Conversation
- MongoDB에서 PostgreSQL로 마이그레이션 (Prisma 사용) - Article 스키마 작성 및 CRUD API 구현 (Offset 페이지네이션 및 검색 포함) - 중고마켓/자유게시판 댓글 API 분리 및 구현 (Cursor 페이지네이션 포함) - 데이터 모델 간 onDelete 설정 및 데이터베이스 시딩 코드 작성 - 공통 에러 처리 및 적절한 HTTP 상태 코드 적용
-> 잘 하셨습니다! 좋은 방법이에요
-> 멘토링에서 mergeParams에 관해 이야기하긴했지만 아래 내용들을 잘 알고 계시면 좋을 것 같아요! 항상 이해가 안될때는 AI에게 mermaid, excllidraw와 같은 시각화 툴을 이용해서 지금 어떻게 되어있는 시각화를 그려달라고 하면 좋습니다.
-> 라우터 자체는 적절히 잘 설계된 것 같으나 추후 댓글이라는 기능이 다른 도메인에서도 사용된다고 하면 중복되는 코드들이 많아질 수도 있을 것 같아요 그때는 댓글 router를 별도로 파서 request 바디에 어디에 해당하는 댓글인지 받아서 처리하는 방법도 있을 것 같습니다. 문제는 아직 없고 좋은 것 같습니다!
-> 이제 UUID에도 버전에 따라 갖고있는 특성이 다르다보니 적절한 아이디들을 잘 설정하시면 좋습니다. UUIDv4를 기준으로 설명했을 때 완전 랜덤이고 ID기반 정렬이나 조회가 ID만으로 이뤄지는 경우 혹은 기타 다른 필드를 통해 조회가 이뤄지면 괜찮고 CUID의 경우 시간순으로 정렬할 수 있다보니 Id 추후 DB에서 인덱스라는 것을 통해 성능 최적화 측면에서 고려되어야할 부분이기도 합니다. 대부분의 도메인에 이미 적절한 ID 선정 모범사례들이 있으니 잘 리서치하고 적용하시면 좋습니다. |
reach0908
left a comment
There was a problem hiding this comment.
코드 리뷰 - [백은결] sprint6
전체적으로 MongoDB에서 PostgreSQL로의 마이그레이션이 잘 진행되었습니다. Repository 패턴 도입, zod를 활용한 유효성 검사, Graceful shutdown 등 좋은 설계가 많이 보입니다.
요약
- 잘한 점: Repository 패턴, validate 미들웨어, onDelete Cascade, 에러 핸들링 구조
- 개선 필요: 라우트 경로 슬래시, 댓글 필터링, Article 스키마 필드
아래 인라인 코멘트들을 확인해주세요. 👇
| @@ -0,0 +1,96 @@ | |||
| import { prisma } from '#db/prisma.js'; | |||
There was a problem hiding this comment.
[좋습니다 👏] Repository 패턴 도입이 훌륭합니다!
데이터베이스 로직을 별도 레이어로 분리하여:
- 라우트 핸들러가 깔끔해짐
- 테스트 시 mock 주입 용이
- 나중에 ORM 변경 시 이 레이어만 수정하면 됨
좋은 아키텍처 선택입니다.
| if (!['body', 'query', 'params'].includes(target)) { | ||
| throw new Error( | ||
| `[validate middleware] Invalid target: "${target}". Expected "body", "query", or "params".`, | ||
| ); |
There was a problem hiding this comment.
[좋습니다 👏] 범용 validate 미들웨어가 매우 잘 설계되어 있습니다!
target파라미터로 body/query/params 유연하게 처리- zod 스키마와 깔끔하게 통합
- 개발/운영 환경별 에러 응답 분리
재사용성이 높고 확장하기 좋은 패턴입니다.
| createdAt DateTime @default(now()) | ||
| updatedAt DateTime @updatedAt | ||
|
|
||
| article Article? @relation(fields: [articleId], references: [id], onDelete: Cascade) |
There was a problem hiding this comment.
[좋습니다 👍] onDelete: Cascade 설정이 잘 되어 있습니다! 부모 레코드(Article/Product) 삭제 시 연관 댓글이 자동으로 삭제되어 데이터 무결성이 유지됩니다.
|
|
||
| model Article { | ||
| id String @id @default(cuid()) | ||
| title String |
There was a problem hiding this comment.
[개선 필요] 요구사항에 따르면 Article에 image 필드와 좋아요순 정렬을 위한 likeCount 필드가 필요합니다.
model Article {
id String @id @default(cuid())
title String
content String
image String? // 이미지 URL (optional)
likeCount Int @default(0) // 좋아요순 정렬용
comments Comment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}| @@ -0,0 +1,64 @@ | |||
| // Prisma 에러 코드 상수 | |||
There was a problem hiding this comment.
[좋습니다 👏] 에러 메시지를 상수로 중앙 관리하는 것이 좋습니다!
- 일관된 에러 메시지 유지
- 다국어 지원 시 이 파일만 수정하면 됨
- 오타 방지
HTTP_STATUS와 함께 사용하여 응답의 일관성이 높습니다.
|
|
||
| // GET /api/products/:productId/comments - 특정 상품의 댓글 목록 조회 | ||
| productCommentsRouter.get( | ||
| '/', |
There was a problem hiding this comment.
[개선 필요] 특정 상품의 댓글만 조회해야 하는데, productId로 필터링하지 않고 전체 댓글을 조회하고 있습니다.
req.params에서 productId (또는 mergeParams로 전달된 id)를 가져와서 필터링해야 합니다.
const { id: productId } = req.params; // mergeParams로 부모의 :id 접근
const comments = await commentRepository.getProductCommentsWithCursor({
productId, // 필터 추가
cursorId,
limit,
});|
|
||
| // GET /api/articles/:articleId/comments - 특정 게시글의 댓글 목록 조회 | ||
| articleCommentsRouter.get( | ||
| '/', |
There was a problem hiding this comment.
[개선 필요] 상품 댓글과 동일한 이슈입니다. articleId로 필터링이 필요합니다.
const { id: articleId } = req.params;
const comments = await commentRepository.getArticleCommentsWithCursor({
articleId,
cursorId,
limit,
});| async function getProductCommentsWithCursor({ cursorId, limit = 10 }) { | ||
| const comments = await prisma.comment.findMany({ | ||
| take: limit, | ||
| skip: cursorId ? 1 : 0, // cursor 포함 여부 조절 |
There was a problem hiding this comment.
[버그 🐛] Comment의 id는 cuid() (문자열)인데, Number(cursorId)로 변환하고 있습니다. cuid는 숫자가 아니므로 항상 NaN이 됩니다.
- cursor: cursorId ? { id: Number(cursorId) } : undefined,
+ cursor: cursorId ? { id: cursorId } : undefined, // 문자열 그대로 사용| description: { | ||
| contains: search, | ||
| mode: 'insensitive', | ||
| }, |
There was a problem hiding this comment.
[사소함] Comments가 Commnets로 오타가 있습니다.
- function findProductWithCommnets(id)
+ function findProductWithComments(id)| "main": "src/server.js", | ||
| "type": "module", | ||
| "engines": { | ||
| "node": ">=22.0.0" |
There was a problem hiding this comment.
[좋습니다 👏] subpath imports (#constants, #repository 등) 설정이 깔끔합니다!
상대 경로 지옥(../../../)을 피하고 import 경로가 훨씬 읽기 쉬워집니다. 모던 Node.js 프로젝트의 좋은 패턴입니다.
요구사항
기본 요구사항
중고마켓
공통
자유게시판
댓글
🔧 주요 변경사항
mongoDB→PostgreSQL마이그레이션Prisma기반 스키마 설계 및 마이그레이션 적용Article스키마와 요구사항에는 없으나 도메인상 필요하다고 판단하여Comment스키마 추가repository레이어 까지 관심사 분리💬 멘토님께 남기는 메시지
server.js에서 마운트 하는 부분과 라우터 간의 연결이 너무 어렵습니다ㅠㅠ댓글 수정, 삭제 API는 전역 댓글 라우터에서 처리했습니다. 이 구조가 적절한지 궁금합니다.
Article,Product,Comment모두cuid를 사용해서 ID를 만들었는데 실무에서도uuid나cuid를 주로 사용하는지, 아니면 쓰임새에 따라int형 ID를 사용하는 경우도 있는지 궁금합니다.