[nodeJS] Cafe Stamp 프로젝트 - response와 리펙토링 & multer JS

RedPanda·2022년 11월 1일
0

response와 리펙토링

누구도 이해하지 못하는 response

프로젝트의 기능을 테스트까지 간단하게 끝내고 난 후에 "내 서버는 완벽해!" 라고 생각하며 프론트엔드 쪽을 보고 있었다. 그런데 프론트에서 자꾸 500 error로 응답이 가는 현상을 발견했다. 분명히 들어온 값이 잘못 들어왔는데 서버 문제라고 하니 골치가 아팠다. 또한, 서버에서 들어오는 콘솔도 에러를 제대로 확인해주지 못했다. 내가 처리한 모든 response는 누구에게도 제대로 응답해주지 못하는 response가 되어버린 것이다.

나는 어떻게하면 정확한 응답을 보내주며, 어떤 응답을 서버에서 처리하는지 알아내고 싶었다. 그러기위해 서버에서 어떠한 응답을 보내줘야 하는지 알아보기로 했다.

status code를 알아보자

[HTTP Status Code - REST API]
위 링크를 따라가보면 정말 많은 코드가 있다. 수업때 이 많은 코드들을 전부 분류해서 처리하는 것은 무리가 있다고 했다. 따라서 나는 최대한 큰 영역에서부터 잘라보았다.

  • *200 : OK - 처리에 성공했다.
  • *400 : Bad Request - 요청 값에 문제가 있다.
  • 401 : Unauthorized - 인가(인증)된 사용자가 아니다.(로그인 X)
  • 403 : Forbidden - 접근 권한이 없는 사용자이다.
  • *404 : Not Found - URL을 찾을 수 없다.
  • 419 : Token Expired - 토큰이 만료된 사용자이다.
  • *500 : Internal Error - 서버에서 처리 중 오류가 발생했다.

크게 이렇게 네 분류(*)로 나누었는데, 여기서 회원 인증에 대한 코드를 몇개 더 추가하였다.

직접 짜본 response 객체

이번에는 프론트에도 세부적인 응답값을 확인하기 위해 더 자세하게 응답 코드를 짜보았다. 다음은 내가 직접 짜본 응답 처리 객체이다.

// libs/error.js

const resCode = {
  REQUEST_SUCCESS: {
    code: 200,
    message: "Successful Request",
  },
  NO_SEARCH_DATA: {
    code: 204,
    message: "No Search Data",
  },
  BAD_REQUEST_LACK_DATA: {
    code: 400,
    message: "More Data is Required!",
  },
  BAD_REQUEST_NO_USER: {
    code: 400,
    message: "No User",
  },
  BAD_REQUEST_EXIESTED: {
    code: 400,
    message: "Data Already Existed!",
  },
  BAD_REQUEST_WRONG_DATA: {
    code: 400,
    message: "Request Failed",
  },
  FORBIDDEN_ERROR: {
    code: 403,
    message: "Forbidden Error",
  },
  TOKEN_EXPIRED_ERROR: {
    code: 419,
    message: "Token is Expired",
  },
  UNAUTHORIZED_ERROR: {
    code: 401,
    message: "Unauthorized User",
  },
  TYPE_ERROR: {
    code: 500,
  },
  SEQUELIZED_ERROR: {
    code: 500,
  },
  UNEXPECTED_ERROR: {
    code: 500,
  },
  NOT_FOUND: {
    code: 404,
    message: "URL Not Found!",
  },
};

module.exports = resCode;

각 객체에 코드와 메세지를 넣어 JSON으로 값을 전달하도록 했다. 이렇게 코드를 짜니 문제가 발생했다.

javascript의 깊은 복사

json에 data를 담아야할 때에 그냥 담게 되면 원본 객체가 깨지는 현상이 발생했다. 그래서 깊은 복사로 객체를 복사해야 했다. 다음은 깊은 복사에 관한 코드이다.

// 로그인시 토큰을 생성하고 토큰과 성공 코드를 응답하는 경우
const response = resCode.REQUEST_SUCCESS;
response.token = accessToken; // 이렇게 토큰을 추가하는 경우 원본을 훼손
return res.status(response.code).json(response);

const response = JSON.parse(JSON.stringify(resCode.REQUEST_SUCCESS));
// stringify로 깊은 복사 후에 parse로 객체 변환
response.token = accessToken;
return res.status(response.code).json(response);

리펙토링과 한계

초기에 코드를 다 짠 후에 보니 난장판이였다. 한눈에 알아보기에 불편한 코드와 잘못된 메세지 또는 응답값, 이곳저곳에서 처리한 예외처리 등등 다른 사람이 보기 힘들게 코드를 짜놓았다. 백엔드 개발에 두 명이 참여했기에 한눈에 보기 편한 코드를 짜야했다. 그래서 response도 직접 만들어보았고, 예외처리도 위쪽에서 순서대로 해놓았다.

결과적으로는 잘모르는 사람도 이전 코드와 비교하여 훨씬 코드를 알아보기 쉬워졌다. 필요할때 응답값을 빼오는 것이 생각보다 편리한 기능이였다. 또한 모든 catch문(500 Error)를 처리할 에러처리 미들웨어로 보내어 모든 서버 에러를 전보다 깔끔하게 처리할 수 있었다. 다음은 에러처리 미들웨어이다.

// 이전의 catch문
catch (error) {
    console.log("ERROR RESPONSE -", error);
    return res.status(500).json(error)
  }
// 수정된 catch문
catch (error) {
    console.log("ERROR RESPONSE -", error);
    error.statusCode = 500;
    next(error);
  }

// app.js의 에러처리 미들웨어
app.use((err, req, res) => {
  console.log(req.query.error);
  console.log("error name - ", err.name || "notFound");
  res.locals.message = err.message;
  let response = {};
  // 서버 에러 코드에 대한 세부적인 처리를 해보려 함
  if (err.name === "SequelizeDatabaseError") {
    response.message = "DB ERROR";
  } else if (err.name === "TypeError") {
    response.massage = "TypeError - dataType problem or DB is not allowed Null";
  }
  res.locals.error = process.env.NODE_ENV !== "production" ? err : {};
  res.status(err.status || 500).json(response || err);
});

사실 책에서 주어진 코드를 조금 바꾼게 다이다. 중점적으로 봐야하는 것은 catch문의 처리이다. next로 에러를 보냄으로써 서버 에러처리를 관리할 때는 에러처리 미들웨어만 보면 된다는 점이 위 코드의 핵심이다.

한계점 또한 존재했다. 직접 만든 코드에서 400 에러를 세부적으로 처리하는 경우에 보안이 취약하다는 단점이 있다. 이번에는 간단한 프로젝트였기에 프론트에서 값을 확인하기 위해 이렇게 보냈다고 하지만 실제로 배포하려면 이렇게 보내는 것은 큰 문제가 있다. 깊은 복사 또한 또 하나의 문제이다. 깊은복사의 경우에 속도가 빠르지 않아 처리 속도가 느려질 우려가 있다.

http-response-codes

내가 만든 응답처리 객체는 부족한 점이 많았다. 그래서 npm을 뒤져보다가 'http-response-codes'를 발견하였다. 각 응답값에는 메세지, 코드 등이 담겨있어 사용하기 정말 편할 것 같았다. 대신에 코드 크기가 커질 것 같다는 단점이 있었는데 일단 사용해보고 결정을 해보아야겠다.

multer - 사진 처리 미들웨어(모듈)

이번 프로젝트에서 카페 이미지와 아이콘을 저장할 일이 생겼다. 수업때 한번 사용해보아서 쓰기 쉬울 줄 알았는데 마지막까지 고생시키는 녀석이었다.
그래서 이번엔 multer에 대해 알아보고, 사용법도 같이 알아보고자 한다.

multer란?

multer는 사진같은 텍스트가 아닌 것들을 요청받았을때 처리를 가능하게 해주는 모듈이다. 정확하게는 멀티파트 폼 데이터를 처리해주는 미들웨어이다. npm에서는 이렇게 설명한다.

Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. It is written on top of busboy for maximum efficiency.

http로 내 서버 내에서 통신하는 경우에도 애는 먹었지만 사용해보았다. 하지만 이번에는 프론트 서버에서 전달받은 데이터를 처리하는 경우이기도 하고, 사용하는 법도 익숙하지 않아서 더 시간을 썼던 것 같다.

multer의 사용법

multer의 사용법은 이러하다.

1.app.js에서 들어올 img 데이터들을 처리한다.
2. middleware에서 img를 정제한다.
3. api에서 req.files의 형태로 받아온다.

설명하는 것도 어렵지만 쓰는 법도 여간 쉬운일이 아니였다. 코드를 보면서 천천히 살펴보자.

// app.js
app.use("/uploads", express.static(path.join(__dirname, "uploads")));
// app.js에서 이 코드를 추가해준다.
// 주의할 점은 현재 디렉토리에 uploads 폴더가 존재해야 한다.

// middleware.js
const multer = require("multer");

exports.upload = multer({
  storage: multer.diskStorage({
    destination: function (req, file, cb) {
      cb(null, "uploads/");
    },// cb 콜백함수를 통해 전송된 파일 저장 디렉토리 설정
    filename: function (req, file, cb) {
      cb(null, new Date().valueOf() + path.extname(file.originalname));
    }, // cb 콜백함수를 통해 전송된 파일 이름 설정
  }),
});

// ./routes/cafe.js
// router에서 미들웨어로 적용
router.put(
  "/update-cafe/:cafeId",
  verifyToken, // 토큰 확인
  upload.fields([
    { name: "icon", maxCount: 1 },
    { name: "img", maxCount: 1 },
  ]), // 받은 파일들을 구별하기 위해 key, value 형태로 받음
  updatecafe // api 실행
);

// ./controllers/cafe.js

exports.updatecafe = async (req, res, next) => {
  try {
    // 수정할 값은 하나만 들어와도 수정되어야 한다.
    const { cafeName, location, businessNum } = req.body;
    const { cafeId } = req.params;
    const formData = req.files; // 미들웨어를 지나면 이렇게 req에 담김
    let icon = "";
    let img = "";
    if (formData) {
      icon = formData["icon"] ? formData["icon"][0].filename : null;
      img = formData["img"] ? formData["img"][0].filename : null;
      // undefined에 인덱스를 찍으면 오류가 나므로 예외처리를 해준다.
    } else {
      icon = null;
      img = null;
    }
.
.
.

위에서 upload.fields는 들어온 데이터들을 객체 형태로 저장하기 위해 사용한다. 예를 들어, icon에 a, img에 b를 넣어 요청했다고 한다면

req.files = {
  icon : [{a}],
  img : [{b}],
}

이러한 방식으로 저장된다. maxcount라는 것은 데이터를 몇개까지 허용할 것인지 확인하는 것이다. 그래서 value는 배열로 저장되는 것이다.

upload.array()와 upload.single()도 존재한다. 각각은 요청한 formdata의 key값을 파라미터로 넣어 사용한다. 자세한 사용법은 아래의 블로그를 확인해보도록 하자.

multer의 다양한 데이터 처리 방법 링크

여담

response값을 꼭 다시 처리하고 리펙토링 또한 다시 해보아야 한다. 이번 프로젝트는 물론이고 앞으로 프로젝트를 하면서 어떻게 효율적으로 코드를 짤 것인지는 개발자의 숙명이라고 생각한다. 물론 회사 메뉴얼이 있다면 그것을 따르는 것이 최우선이지만, 그전까지 코드를 짤 때 최소한 누구나 알아볼 수 있는 코드를 짜는 것이 중요하다고 생각한다.

multer 또한 써보면서 정말 어려웠지만 써보니 이렇게 편리한 기술이 있다는 것에 감사해야겠다고 생각했다. 다른 모듈들도 확인하고 직접 써보는 연습을 해보아야겠다고 생각했다.

profile
끄적끄적 코딩일기

0개의 댓글