항해 5주차 회고
팀 프로젝트
주제
WISH - 내가 갖고 싶은 제품을 업로드하고 의견을 공유하는 커뮤니티!
기술 스택
Front-end
Pure CSS
React
Back-end
Node.js
Express
MongoDB
기능
백엔드의 관점에서, 기능적 요소는 게시판과 크게 차이가 없었다. 따라서 간단하게 적어보았다.
회원가입
nickname과 email, password를 받아서 회원 정보를 DB User collection에 저장한다. password는 hash 후 저장한다.
로그인
사용자 로그인 api 호출 후 백엔드에서 생성한 토큰을 응답으로 보내준다. 프론트엔드에서는 토큰을 local storage에 저장 후 사용한다.
게시물 생성 + 이미지 업로드 , 수정, 삭제
사용자가 공유하고자 하는 제품의 이미지 파일을 업로드하고, 세부 내용을 작성한다. 내용은 DB에 저장하고, 이미지 파일은 서버 로컬에 저장한 뒤 그 경로를 DB - Posting - imageUrl에 저장한다. 게시물의 수정, 삭제는 본인이 작성한 게시물일 경우에만 수행할 수 있도록 하였다.
댓글 작성, 수정, 삭제
특정 게시물에 대해 댓글을 작성하면, DB - Comment 에 저장한다. 이때 댓글은 어떤 게시물의 댓글인지 식별할 수 있어야 하므로, 해당 게시물의 id도 함께 저장한다.
좋아요 기능
사용자가 게시물에 좋아요를 남길 수 있다. DB에서는 Like 스키마를 작성한 뒤 Posting 스키마에 embed하였다. Posting에 있는 like 속성은 Like 스키마의 배열이다. 사용자가 좋아요 api를 다시 호출할 경우, 좋아요를 취소하도록 기능을 구현했다.
기술적 문제점과 해결 과정
이미지 업로드
문제 #1 : 데이터 포멧에 대한 사전 협의
초기에 multer모듈을 사용할 계획을 하고 api를 준비했다. 큰 실수를 하나 저질렀는데, 프론트엔드 개발자 분과 데이터 포맷에 대한 합의 없이 독단적으로 api 개발을 진행했다는 점이다. multer 공식 문서에 나와 있듯이, multer는 multipart/form-data에 대해서만 파일 업로드를 지원한다. 이걸 개발 시작 전 서로 협의를 했어야 했는데, 목요일 테스트를 하면서 협의가 되어있지 않다는 걸 알게 되었다. 프론트엔드 개발자님은 application/json으로, 이미지 파일 형식은 base64로 보내주고 계셨다.
문제 #2 : base64 형식의 이미지 처리와 식별
결국 multer는 사용할 수 없는 상황이 되었다. 혹시나 해서 base64 텍스트를 파일로 저장하여 정적 파일로 클라이언트에 제공해봤지만, 이미지가 나오지 않았다. 또한 각각의 파일마다 고유한 이름이 필요했다.
해결
- base64 형태의 이미지에서 버퍼 추출
다행히 구글링 결과 base64 형태의 텍스트를 버퍼로 만들어주는 방법이 있었다.
const decodeBase64Image = (dataString) => {
let matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/),
response = {};
if (matches.length !== 3) {
return new Error("Invalid input string");
}
response.type = matches[1];
response.data = Buffer(matches[2], "base64");
return response;
};
- 고유 id 부여
백엔드 팀원분께서 좋은 모듈을 소개시켜 주셔서 아주 간단하게 문제를 해결할 수 있었다. uuid라는 모듈을 사용해서 파일 이름에 고유 id를 붙일 수 있었다.
import { v4 as uuidv4 } from "uuid";
...
const fileName = uuidv4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
- node 내장 모듈 fs를 사용해서 파일 저장
천운이었는지, 마침 얼마전 사용한 fs 모듈이 생각났다. 이를 활용해 서버 로컬 폴더에 파일을 저장했다.
import fs from "fs/promises";
...
const imageUrl = `uploads/${fileName}.jpeg`;
try {
const result = await fs.writeFile(imageUrl, imageBuffer.data);
console.log(result);
//성공하면 req에 새로운 객체 생성
req.imageUrl = { imageUrl };
next();
} catch (err) {
logger.error(err);
//에러 발생시, 작업 중단하고 클라이언트에게 메세지 전송
return res.status(400).send({ msg: "파일 저장에 실패하였습니다." });
파일 변환과 저장은 미들웨어로 처리했다. 실제 DB와의 상호 작용은 콜백에서 진행하므로, req.imageUrl이라는 객체를 만든 뒤 next()를 호출했다.
글을 작성하고 나니 간단하게 해결한 것 같아보이는데, 이것만 6시간은 붙잡고 있었다. 죽는 줄....
한계
- 서버 스토리지 문제
현재 서버는 AWS EC2를 사용하고 있다. 자칫하면 요금 폭탄을 맞을 수 있겠다. 다음엔 S3에 파일을 업로드하는 방법으로 바꿀 예정이다. - 이미지 확장자
fs 모듈을 사용해서 이미지를 저장하는 과정에서, 모든 이미지의 확장자가 jpeg가 되어버린다. 추가적인 분기 처리가 필요할 것 같다.
회고
협업을 하면서......
처음으로 내가 잘 모르는 분야와 협업하면서, 실무에서도 있을 법한 문제들을 겪었다. 우선 API 명세가 매우 중요하다는 것을 깨달았다. 시간이 부족했던 탓에 명세서를 백엔드 개발팀 단독으로 작성했다. '프론트엔드도 이대로 api를 호출하겠지'라는 안일한 생각 때문에 일을 여러번 하는 경우가 많았다. 어느 분야던 간에 인터페이스 명세는 정말 중요하다는 것을 다시 깨달았다.
반면 백엔드 팀워크는 정말 괜찮았다고 생각한다. 파일 분리도 적절하게 한 덕분에 git merge confilct도 잘 나지 않았다. 또한 커밋은 최대한 작게 쪼개어 작업했고, 브랜치 관리도 아주 매끄럽게 진행했다. 서로 잘 진행되지 않는 부분은 밤을 새어가며 서로 도와주었다.
과제 제출 기한은 지키지 못했지만, 그래도 끝까지 마무리 하자! 라는 생각으로 6시간 추가 작업을 통해 결국 배포에 성공했다.
개인적으로......
이번 프로젝트로, nodejs + express + mongoDB 프로젝트만 4번째 진행했다. 이제야 왜 사람들이 문법이 크게 중요하지 않다고 하는지 깨달았다. 결국 뭘 만드냐에 따라 사용하는 문법이 정해져 있는 것 같다.
내 문제점도 깨달았다. 나는 문제를 직면했을 때 해결하는 능력은 꽤 뛰어난 것 같다. 반면, 새로운 시도를 하려는 노력은 많이 부족하다고 느꼈다. 그래서 새로운 시도를 하는 다른 분들을 보면서, 알게 모르게 자괴감을 느꼈던 것 같다. 좀 더 노력해야겠다.
API 명세
https://www.notion.so/f84e7d3cee424ff193b3fc0fa50285e1?v=758d90eb77974636b280b62d45bacd3f
Github
https://github.com/yeonjeseo/Hangstagram-Back
서비스 URL
http://kbumsoo.s3-website.ap-northeast-2.amazonaws.com/
Author And Source
이 문제에 관하여(항해 5주차 회고), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@goatyeonje/항해-4주차-회고저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)