본문 바로가기

개발/프로그래머스 데브코스

프로그래머스 데브코스 42일차 with. TS 웹 풀스택

📚요약

지난 시간은 JWT를 활용해 API의 수정이 있었습니다. 이번 시간에는 인가(authorization)의 모듈화를 하고 나머지 API도 수정을 해보겠습니다. 추가적으로 API 설계 문서와 실제 구현을 비교해 보면서 부족한 부분과 네이밍 등 어색한 부분도 수정해 보겠습니다.

 

📖SQL

📄전체 행(튜플, 레코드) 수(Cardinality) 구하기

테이블의 전체 데이터 수를 구하기 위해서는 두 가지 방법이 있습니다.

  1. count() 활용 : 결과의 행 수를 세는 함수.
  2. SQL_CALC_FOUND_ROWS & found_rows() 활용 : SQL_CALC_FOUND_ROWS을 통해 테이블의 모든 행 수를 계산하고, found_rows()를 통해 이전 결과의 행 수를 출력.
SELECT count(*) FROM table;

SELECT SQL_CALC_FOUND_ROWS * FROM table;
SELECT found_rows();

두 가지 모두 전체 행 수를 구할 수 있는 방법이지만 MySQL에서는 2번 방법은 deprecated 된다고 하며 1번 방법을 추천하고 있습니다.

 

MySQL :: WL#12615: Deprecate SQL_CALC_FOUND_ROWS and FOUND_ROWS

WL#12615: Deprecate SQL_CALC_FOUND_ROWS and FOUND_ROWS Affects: Server-8.0   —   Status: Complete Description Requirements MySQL has a nonstandard query modifier called SQL_CALC_FOUND_ROWS. When in use on a SELECT with LIMIT, it attempts to calculate h

dev.mysql.com

 

📖Authorization 모듈화

📄구현

API 중에서 로그인할 때와 안 할 때가 하나의 요청에 구현되는 경우가 있습니다. 이때 구현에는 크게 두 가지 방법이 있습니다.

1. 요청(request)에 authorization을 확인하고, 비어있으면 빈 토큰을 반환하는 방법

더보기
// authorization.js
const dotenv = require("dotenv");
const jwt = require("jsonwebtoken");

dotenv.config();

const decodeUser = (req) => {
  try {
    const token = req.headers.authorization;

    if (token) {
      const user = jwt.verify(token, process.env.PRIVATE_KEY);

      return user;
    }

    return token;
  } catch (err) {
    console.log(err.name);
    console.log(err.message);

    return err;
  }
};

module.exports = { decodeUser };

// 모듈 사용 코드
const { decodeUser } = require("../authorization");
const jwt = require("jsonwebtoken");

const userId = decodeUser(req)?.userId;

if (userId instanceof jwt.TokenExpiredError) {
  return res.status(StatusCodes.UNAUTHORIZED).json({
    message: "로그인 세션 만료됨.",
  });
} else if (userId instanceof jwt.JsonWebTokenError) {
  return res.status(StatusCodes.BAD_REQUEST).json({
    message: "토큰이 이상합니다. 확인해주세요",
  });
}

// ... 구현

2. 요청(request)에 authorization을 확인하고, 새로운 에러를 만들어서 반환하는 방법

더보기
// authorization.js
const dotenv = require("dotenv");
const jwt = require("jsonwebtoken");

dotenv.config();

const decodeUser = (req) => {
  try {
    const token = req.headers.authorization;

    if (token) {
      const user = jwt.verify(token, process.env.PRIVATE_KEY);

      return user;
    } else {
      throw new ReferenceError('jwt must be provided');
    }
  } catch (err) {
    console.log(err.name);
    console.log(err.message);

    return err;
  }
};

module.exports = { decodeUser };

// 모듈 사용 코드
const { decodeUser } = require("../authorization");
const jwt = require("jsonwebtoken");

const userId = decodeUser(req)?.userId;

if (userId instanceof jwt.TokenExpiredError) {
  return res.status(StatusCodes.UNAUTHORIZED).json({
    message: "로그인 세션 만료됨.",
  });
} else if (userId instanceof jwt.JsonWebTokenError) {
  return res.status(StatusCodes.BAD_REQUEST).json({
    message: "토큰이 이상합니다. 확인해주세요",
  });
} else if (userId instanceof ReferenceError) {
  // ... 구현
}

저는 1번 방법으로 구현했습니다. 새로운 에러를 반환하면 코드가 복잡해진다고 느꼈고, 코드의 중복이 발생한다고 느꼈습니다.

 

📖좋아요 & 장바구니 API

📄구현

지난 시간에 구현했던 부분을 모듈화 시켜서 구현했기 때문에 결과는 달라지지 않았습니다.

 

📖책 API

📄구현

개별 책 조회 로그인 했을 때 & 로그인 하지 않았을 때

책의 전체 목록 조회는 인가(authorization)가 필요 없고, 개별 책 조회를 했을 때 사용자의 좋아요 여부를 확인하기 위해서 인가가 필요합니다. 이때 로그인을 하지 않아도 책의 상세 정보를 볼 수 있도록 해야 하기 때문에 로그인을 한 경우에만 좋아요 여부(is_like)를 응답(Response)에 담아서 보내줍니다.

전체 책 목록 조회

전체 책 목록 조회 부분에서 수정된 부분은 단순한 책 목록만 전달하는 것이 아니라 하단의 목록을 출력하고, 현재 페이지를 알려주기 위해서 pagenation 정보를 추가로 보내줍니다.

🍯tip! 여러 SQL을 보내고 싶다면 mysql2 모듈에서 제공하는 함수를 사용해 보낼 수 있다. 자세한 내용은 블로그공식 사이트를 참고하면 좋다.

 

이때 강사님의 코드가 Q4질문을 발생시켰습니다.

 

📖주문 API

📄구현

주문하기 & 주문 목록 조회
주문 상세 조회 & 주문 상세 조회 실패

authorization 모듈을 적용시켜서 모두 정상 동작하는 것을 확인했습니다.

 

+a) 마지막으로 API 설계 문서의 통일성을 위해 구현된 부분되 맞추는 동시에 이름을 통일하는 작업을 했습니다. 저는 request는 camelCase로 하고, response는 snake_case로 했는데 둘을 구분하지 않고 통일해야하는지 고민입니다.

 

자세한 코드는 Github에서 확인할 수 있습니다.

 

ProgrammersSchool/PROJECT-BOOKSHOP at main · nulzi/ProgrammersSchool

프로그래머스 데브코스에서 학습하는 것들을 모아두는 레포. Contribute to nulzi/ProgrammersSchool development by creating an account on GitHub.

github.com

 

❔▪❓

Q1. 에러가 발생했을 때 결과에 대한 처리가 동일한 코드가 중복이 되는데 어떻게 모듈화를 해야 할까?

const userId = decodeUser(req).userId;

if (userId instanceof jwt.TokenExpiredError) {
  return res.status(StatusCodes.UNAUTHORIZED).json({
    message: "로그인 세션 만료됨.",
  });
} else if (userId instanceof jwt.JsonWebTokenError) {
  return res.status(StatusCodes.BAD_REQUEST).json({
    message: "토큰이 이상합니다. 확인해주세요",
  });
}

 

Q2. 모듈화를 시키는 기준이 있을까?

 

Q3. 좋아요 API를 구현하는 두 함수가 결국 sql과 HTTP Method만 다른데 하나로 합치는 건 안 되는 건가?

 

Q4. query()함수를 연속으로 실행시켜도 문제가 없는 것인가?

    let allBooksRes = {};

    conn.query(sql, values,
        (err, results) => {
            if (err) {
                console.log(err);
                //return res.status(StatusCodes.BAD_REQUEST).end();
            }
            console.log(results);
            if (results.length) {
                allBooksRes.books = results;
            } else {
                res.status(StatusCodes.NOT_FOUND).end()
            }
        }
    )

    conn.query(sql,
        (err, results) => {
            if (err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }

            allBooksRes.pagination = pagination;

            return res.status(StatusCodes.OK).json(allBooksRes);
        }
    )

 

다음 시간에 계속...

 

출처 & 참고

김송아 강사님의 강의