[이서라] sprint14 - react5#70
Hidden character warning
Conversation
devbini
left a comment
There was a problem hiding this comment.
🎉 고생 많으셨습니다!
백엔드 API와 연결하고 PostgreSQL + Prisma 구조 구현까지.. 내용이 많았음에도 axios 인스턴스를 따로 두고, 요소별 라우트를 한 흐름으로 묶으려 한 점 등 여러모로 구조 기획과 개발에 있어 재능이 있어보이기도 했어요.
특히 서버 쪽에서 모델을 세분화하여 나눠두고, 태그를 단순 문자열 배열로만 처리하지 않고 connectOrCreate로 재사용하려고 한 점이 기억에 남네요.
댓글 역시도 onDelete: Cascade까지 연결해둔 부분까지 생각해봤을 때 관계형 모델링에 대해 제대로 이해하고 쓰고 있구나- 하는 마음도 들었어요.
또... 입력값 검증을 붙이려 한 점도 좋았네요.
보통 일반적으로 처음 API를 만들 때는 일단 DB에 넣는 흐름만 생각하기 쉬운데,
검증 계층을 두려는 시도 자체가 좋은 습관이라고 생각합니다. (테스트까지 포함해서요!)
다만 프론트에서 보내는 데이터 타입과 서버 validator가 맞지 않아 일부 기능이 막힐수도 있는 점이라던가, 라우트는 있지만 컴포넌트에서 아무것도 렌더링하지 않는 부분 등 UX에 있어서 문제가 될 법한 부분도 분명히 있었어요.
전반적으로... 이번 스프린트 목표인 프론트 화면과 직접 만든 API를 연결한다는 내용은 잘 수행한 것 같아요. 앞으로는 응답 필드와 타입 변환, 또.. UX적으로 생각했을 때 렌더링을 어떻게 할지 등 세밀한 연결부를 조금 더 단단하게 맞추면 훨씬 안정적인 코드가 될 것 같습니다.
너무나 고생 많으셨고, 파이팅입니다! 👍
| const data = Object.fromEntries(formData.entries()); | ||
|
|
||
| try{ | ||
| const response = await axios.post('/tasks', data); |
There was a problem hiding this comment.
🚩 Check Point
상품 등록 폼에서 FormData를 객체로 바꾼 뒤 그대로 서버에 보내고 있네요.
이렇게 만들면 price는 숫자가 아니라 문자열로 들어가고, tags도 배열이 아니라 문자열로 들어갑니다.
그런데 서버쪽의 CreateItem은 price: number, tags: string[]를 받을 준비릃 하고 있어서 assert(req.body, CreateItem) 단계에서 400 에러가 날 가능성이 있어요.
아니, 날 것 같아요.
프론트에서 보내는 데이터 형태를 미리 맞춰주거나,
서버에서 검증 전에 타입을 정리하는 추가 로직이 있어야 안전할 것 같아요.
| arrayTags = tags.split(",").map((tag) => tag.trim()).filter(t => t); | ||
| } else { | ||
| arrayTags = tags; | ||
| } |
There was a problem hiding this comment.
🚩 Check Point
위 내용과 연결되는 부분인데, tags 타입을 변환하는 코드가 검증 이후에 실행되고 있습니다.
코드를 봤을 때 아마도 문자열 태그도 받아서 배열로 바꾸려는 것처럼 보이는데...
assert가 먼저 실행되기 때문에 문자열 태그는 변환 코드까지 도달하지 못합니다.
또 CreateItem에서는 tags를 optional로 열어두고 있어서...
태그가 아예 없는 요청이 들어오면 이후 tags.split(',')에서 500 에러가 날 수도 있습니다.
지금 조건 분기에서 isArray가 false만 나오면 split을 시도하고 있어서...
body를 정규화한 다음 검증하는 순서로 바꾸면 좋을 것 같아요.
| orderBy: order, | ||
| skip: parseInt((page - 1) * pageSize), | ||
| take: parseInt(pageSize), | ||
| select: { name: true, price: true, createdAt: true, tags: true }, |
There was a problem hiding this comment.
🚩 Check Point
상품 목록 조회에서 select를 쓰면서 id를 안 가져오고 있네요,
프론트의 ItemList는 key={item.id}를 쓰고 있고,
이후 목록에서 상세 페이지로 이동하는 구조를 위해 id가 필요합니다.
지금 구조에서는 목록의 각 상품을 식별할 수 없어서... key 경고가 나거나 상세 페이지를 붙일 때 막힐 것 같아요.
| <svg width="16" height="16" viewBox="0 0 16 16" fill="none"> | ||
| <path d="M10.6 2.59961C12.532 2.59961 14.0756 4.1317 14.1332 6.08398V6.2002C14.1331 7.24212 13.7296 8.14487 13.0609 8.75781L12.8666 8.93652V8.98047C12.7656 9.06604 12.6353 9.17783 12.4838 9.30957C12.1466 9.60273 11.695 10.0004 11.2035 10.4336C10.2217 11.2989 9.07966 12.3083 8.349 12.9395C8.17258 13.0862 7.89856 13.0865 7.72107 12.9414C6.98886 12.309 5.82568 11.2963 4.8363 10.4316C4.34071 9.99852 3.88886 9.6028 3.55701 9.31152C3.40687 9.17974 3.28741 9.07349 3.19958 8.99609V8.95117L3.02478 8.77539C2.33102 8.08163 1.93304 7.16559 1.93298 6.2002V6.08496C1.98977 4.21111 3.58945 2.66699 5.46716 2.66699C5.74534 2.6671 6.11403 2.76392 6.48181 2.96191C6.83372 3.1514 7.14175 3.41108 7.34412 3.7002C7.64091 4.30642 8.51663 4.29878 8.79919 3.67773C8.96446 3.37509 9.26407 3.10098 9.62537 2.89941C9.99296 2.69441 10.3596 2.59963 10.6 2.59961Z" stroke="#4B5563" strokeWidth="1.2"/> | ||
| </svg> | ||
| {item.favoriteCount} |
There was a problem hiding this comment.
☕ Thinking
favoriteCount가 제 눈에 안 보이는 건.. 제 문제일까요!?
현재 우선 /tasks response에 image / favoriteCount가 없는 것 같아요.
이것 떄문에 오류가 나며 페이지가 죽진 않겠지만, 화면에 undefined로 표시가 된다거나 썸네일이 모두 동일한 default로 보이는 등의 문제가 발생할 것 같아요.
이런 작업을 할 때는
서버 응답 필드와의 매핑 여부를 조금 더 촘촘하게 하는 게 좋을 것 같아요 🤗
| skip: parseInt((page - 1) * pageSize), | ||
| take: parseInt(pageSize), |
There was a problem hiding this comment.
☕ Thinking
이것도 문제는 아니지만,
숫자를 바로 DB로 던지고 있는 것 같아요. 페이지네이션 관련 데이터를..
정상적인 상황에서는 문제가 없지만, 실제 환경에서는 어떤 문제가 나올지 몰라요.
예를 들어- pageSize 데이터가 음수로 들어간다거나, 비정상적으로 엄청 큰 데이터가 들어간다던가...
Math 라이브러리를 활용하면 아마 상/하 제한을 걸 수 있을텐데, 서버 리소스 보호를 생각하는 방법이 하나 더 있지 않을까 싶어 남겨둡니다ㅎㅎ
| res.sendStatus(404); | ||
| } else { | ||
| console.error(err); | ||
| res.status(500).send({ message: err.message }); |
There was a problem hiding this comment.
🚩 Check Point
음.. 이건 조금 위험해요.
요구사항에 500에 대한 응답 에러처리가 있어서 이렇게 해도 되는건지는 잘 모르겠지만..
500의 경우에는 DB 연결 내용이나 구현에 있어 치명적일 수 있는 서버 오류메시지가 사용자에게 그대로 전달되게 됩니다.
가능하면 message를 하드하게 별도로 만들거나, 에러핸들러를 따로 만들어서 관리하는 게 좋을 것 같아요.
블라인드 SQL 인젝션 등, 다양한 보안적 위험요소가 존재한답니다.
자세한 오류 로그는 서버 로그에만 남기는 게 좋아요! 🥹
| assert(req.body, PatchComment); | ||
| const { id } = req.params; | ||
| const response = await prisma.marketComment.update({ | ||
| where: { id }, |
There was a problem hiding this comment.
☕ Thinking
댓글 수정 update 조건에는 댓글 id만 사용하고 있네요.
음.. 현재 구조에서는 부모 id가 다른 URL로 요청해도, 댓글 id만 맞으면 수정이 진행될 수 있어 보입니다. 유지보수시에 실제 서비스에서는 URL이 표현하는 내용과 DB 대상이 어긋나면 디버깅하기 꽤 까다로워요.
그래서.. 수정 전에 itemId/articleId까지 함께 확인하거나, 복합 조건으로 부모 관계를 검증하는 과정이 추가되면 어떨까 하는 희망소원을 말씀 드려봅니다 🤗
📌 요구사항 달성 현황
🛒 중고마켓 페이지 & 프론트엔드 연동
GET /tasks메서드 및 조건 처리 연동 완료defaultImage) 및onError방어 코드로 처리 완료📝 상품 등록 페이지
POST /tasks메서드 연동 완료_id를 기반으로useNavigate를 활용해 해당 상품 상세 페이지로 즉시 이동 로직 구현 완료🖥️ 백엔드 API & DB (MongoDB / Mongoose)
name,description,price,tags,createdAt,updatedAt필드 구성 완료totalCount필드 추가 완료POST): 사용자 입력값을 받아 안전하게 DB에 적재 기능 구현GET): 필수 필드(id,name,price,createdAt) 선별 조회 기능 구현offset방식을 채택하여 무결한 페이징 처리 완료order쿼리를 통한 최신순(recent) 정렬 및keyword쿼리를 이용한name,description단어 검색 기능 구현 완료GET) 완료PATCH) 완료DELETE) 완료.env파일을 통한 환경 변수 관리 및 데이터베이스 연결 문자열(URL) 격리 완료