SNS-Nodebird

SNS-NodeBird 프로젝트 회고록

Heoky 2022. 3. 25. 16:01

1. 프로젝트를 시작하며, Next.js의 도입

처음 프로젝트를 시작하며 순수 React와 Next.js 중 어떤것을 활용하여 진행할지 고민을 했다.
선택하기 앞서 차이점을 찾아보았다. SSR(Server Side Rendering)과 SEO(검색엔진최적화)를 하는데 있어
Next.js가 참 편리하게 되어있었다. 물론 순수 React를 사용하여 프로젝트를 진행해도 구현은 할 수 있으나
다소 복잡한 부분이 있었다. 솔직히 말하자면 나는 시간을 단축하고 싶은 마음이 있었고 한 강의를 통해 Next.js를
학습하며 도입하게 되었다.

Next.js는 SSR을 편리하게 해줄뿐 아니라 Code Spliting 또한 편리하게 되어있어 그저 이를 활용해 코드에
집중할 수 있었다.

2. Next.js 시작하기

Next.js를 도입하며 page와 레이아웃을 구성하는데 정말 편리했다. React의 Router 구성과는 다르게 간단하고
조작법 또한 편리해 구성하는데 큰 어려움은 없었다.

3. Redux와 Redux-saga의 도입

로그인 기능을 구현하는데 있어서 상태관리를 하는데 다소 불편함을 겪었다. 한곳에 몰아서 편리하게 조작하고
필요 시에 한 곳에서 불러오는데 유용한 상태조작 라이브러리 Redux를 도입하게 되었다.

이후 각 요청마다 동기적으로 dispatch하는데 있어 효율성이 떨어져 비동기적으로 dispatch를 하기 위하여
Redux-saga를 활용하게 되었다. 이를 통해 generator의 사용법과 원리를 이해하게 되었고, saga effect 함수에
대해서도 학습할 수 있었다. 필요한 함수만 이용하다보니 솔직히 하나 하나 구체적으로 알지는 못하나 필요시에
문서를 통해 찾아 활용할 수 있다.

4. 로그인

로그인 과정


로그인 기능을 구현하는데 전체적인 흐름과 그 속에서 보안 이슈에 대한 것도 신경써야 했다.
로그인 기능을 구현하는데 있어서 처음 더미데이터로 로그인을 간단하게 구현했다. 이후 node.js 기반
express로 서버의 router를 구성하였고, sequelize를 통해 mysql(관계형 데이터 베이스)을 조작하였다.

프론트에서 로그인 시 넘어오는 사용자의 정보(ID, Password)를 백엔드에서 넘겨 받으면 이를 hashing하여
password의 보안성을 높여준다.

넘겨받은 로그인 정보를 sequelize를 통해 데이터베이스에서 찾는다. 찾은 정보를 json 형태로 프론트에 넘겨준다.
여기까지는 말은 간단하다. 하지만 로그인 기능을 구현하는데 있어 session과 cookie의 개념을 알 필요가 있었고
로그인 기능을 구현하는데 나는 passport를 활용하여 로그인 전략을 세우기로 했다.

먼저 HTTP의 단점인 정보를 유지를 위해 cookie(HTTP의 일종)를 사용해야하며, 사용자의 정보를 로컬 PC에 저장하기 위해
cookie를 사용한다. 이렇게 사용자가 가지고 있는 cookie를 통해 서버에 정보를 요청할 수 있다.

session은 서버에 저장이 되는데 서버에서 사용자의 정보를 가지고 있는것이다. session cookie를 통해 사용자의 cookie를
가지고 session 정보를 찾아 일치하면 반환해준다.

session을 서버에 저장하는데 많은 정보가 있다면 메모리에 무리가 간다. 때문에 passport 로그인 전략을 통해 사용자의
고유 id 값을 하나만 저장하여 이 id를 통해 정보를 복구해 프론트 단에 전달한다.

5. 로그아웃

로그아웃 과정

로그아웃은 가지고 있는 session를 없애고 리셋해주면 된다. 생각보다 로그아웃 기능을 구현하는데 큰 어려움은 없었다.

6. 회원가입

회원가입 과정

회원가입을 구현하는데 사용자가 입력한 정보를 서버에 넘겨준다. 그 전에 프론트 단에서 여러 조건을 만족해야 정보를
서버에 넘길 수 있다.

입력한 password가 일치하는지 검사 후 일치하면 사용자가 입력한 모든 정보를 서버에 넘기게 된다. password 조건도
구체적으로 주어 넘길 수 있다. 이후 정보를 받은 서버에서는 이미 존재하는 사용자 ID가 있는지 찾아본 후 존재한다면
status 403을 보내고 "이미 ID를 사용하는 사용자가 존재합니다." 라고 브라우저에 보여준다. 아니라면 정상적으로
user table에 데이터를 새로 생성해 가입이 완료된다.

7. CRUD, 이미지 업로드

게시글 및 이미지 업로드
이미지 업로드 취소
게시글 수정

 

게시글 삭제

CRUD(Create, Read, Update, Delete) 기능을 구현하는 것은 사실 처음에는 복잡한듯 하나 가장 기초적인 과정이라
생각이 든다. 구글링을 통해서도 많이 찾아 볼 수 있으며, 구현하는 것이 크게 어려운 것은 없다.

다만 이미지를 업로드하는 기능을 구현하는데 낯설고 어렵긴 했다. 나는 multer를 활용하여 이미지를 업로드 했다.
먼저 배포를 하기 전에는 로컬에 이미지를 저장하기 위해 경로와 파일을 프로젝트 파일에 생성했고 이를 프론트에서도
받아오는 이미지 경로를 개발모드 port 3065로 설정하여 브라우저에 보여주었다.

개발모드 때 사용한 코드와 배포 시 사용한 코드를 보자.
(배포 시 lambda에서 이미지 리사이징을 위한 설정도 해주어야한다. / 생략)

// http://localhost:3065/post

// 이미지를 저장할 폴더를 생성
try {
  fs.accessSync('uploads');
} catch (error) {
  console.log('uploads 폴더가 없으므로 생성합니다.');
  fs.mkdirSync('uploads');
}

// multer 개발모드
const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, done) {
      done(null, 'uploads');
    },
    filename(req, file, done) {
      const ext = path.extname(file.originalname); // filename: 고윤혁.png > 확장자 추출(.png)
      const basename = path.basename(file.originalname, ext); // 고윤혁
      done(null, basename + new Date().getTime() + ext); // 고윤혁2021070424239281.png
    },
  }),
  limits: { fileSize: 20 * 1024 * 1024 }, // 파일크기: 20MB
}); // 지금은 하드디스크에 저장하지만 AWS 배포 시 storage 옵션만 S3 서비스로 갈아끼울 예정

// images upload router 개발모드
router.post('/images', isLoggedIn, upload.array('image'), (req, res, next) => {
  console.log(req.files); // 업로드가 어떻게 됬는지 정보들이 담겨있음
  res.json(req.files.map((v) => v.filename)); // 어디로 업로드 되었는지에 대한 파일명들을 프론트로 보내줌
}); // POST /post/images

// multer AWS 배포모드
const upload = multer({
  storage: multerS3({
    s3: new AWS.S3(), // 이렇게까지 하면 S3의 권한을 얻음, key랑 accessKey로
    bucket: 'coding-factory-s3',
    key(req, file, cd) {
      cd(null, `original/${Date.now()}_${path.basename(file.originalname)}`);
    },
  }),
  limits: { fileSize: 20 * 1024 * 1024 }, // 파일크기: 20MB
});

// images upload router AWS 배포모드
router.post('/images', isLoggedIn, upload.array('image'), (req, res, next) => {
  console.log(req.files); // 업로드가 어떻게 됬는지 정보들이 담겨있음
  res.json(req.files.map((v) => v.location.replace(/\/original\//, '/thumb/'))); // original에서 thumb 이미지를 가져옴
  // location 자체에 주소가 담겨있음, PostFrom에 image src에 그대로 전달(backURL 필요 X)
}); // POST /post/images, // upload.array(), upload.single(), upload.none()

 

이외의 기능들

리트윗

 

해시태그 모아보기
해시태그 검색
사용자 게시글 모아보기
팔로잉 팔로우
사용자 프로필 변경

 프로젝트를 마치며 

사실 프로젝트를 진행하며 쉬운건 없었다. 큰 어려움이 없었을 뿐이지 하나 하나 절대 쉬운건 없었고 원리를 이해하고
코드를 이해하며 구현하는데 절대 쉽지는 않았고 시간 또한 꽤 걸렸다.

구현하며 만났던 여러 에러들 또한 해결하는데 진땀을 뺐고 많은 검색을 하며 전전긍긍 부여잡고 해결했던 시간들이었다.
에러들이 너무 많아 어떤 에러를 만났었는지 일일히 다 기억할 수는 없지만 제일 기억에 남는 에러는 CORS 문제였다.
해결하기 위한 방법은 꽤 간단했지만 CORS의 원인과 동작 방식이나 원리를 이해해야 했던 시간들이 나에게는 사실
조금 어렵고 힘들었다.

모든 기능을 구현하며 데이터 또한 관계를 설정하는 것도 매우 쉽지 않았고 멘탈이 여러번 흔들렸으며 복잡하고 어려웠다.
하나 하나 코드를 구현하고 하나 하나 문제를 해결하는데 찾아보는 개념들과 원리 그리고 동작방식들을 보며 해결하는 방법의
코드는 꽤 단순하나 이해하는 과정이 어렵구나 생각이 들었다.
하지만 이 과정 속에서 해결하기 위해 노력하는 시간들이 힘들긴 하나 헛되지 않았고 몰입했던 시간들이 나름 재미있었다.
앞으로 어떤 프로젝트를 하든 비슷한 과정을 많이 겪겠지만 결국 경험 싸움이구나 생각 또한 들었다.
언젠간 에러들과 어떤 문제를 해결하는데 있어서 "아! 나 이거 겪어봤던거다!!" 하며 빠르게 해결할 수 있는 날이 올 것이라
확신한다.

개발은 이 과정들이 모두 성장이고 알아가는 재미이며 결국 해내는 극락의 쾌락이 나를 즐겁게 하고 하루 하루 살아있음을 느낀다.
그렇다고 마냥 즐겁다는 것은 아니다... ㅎㅎ 힘들긴 힘들다...ㅠㅠ ㅋㅋㅋ