프로젝트/도서구매사이트

1023 장바구니 API 구현 - 선택한 장바구니 목록 조회 IN [배열] 적용

thinktank911 2025. 10. 22. 12:37

<장바구니 API 구현>

장바구니 테이블 생성

  • 에러 발생 : errno: 121 "Duplicate key on write or update"
    • 해당 스키마에 똑같은 이름의 FK 제약조건이 있어서 발생하는 에러
    • 여기서는 likes의 user_id FK 제약조건이 같은 user_id로 이미 존재해서 에러가 발생함
  • 해결 방법 : FK 제약조건 이름 정해주기
    • 규칙 : fk_기준 테이블명_참조테이블명_참조키
      ➡️ cartItems.user_id > users.id : fk_cartItems_users_id
      ➡️ likes.user_id > users.id : fk_likes_users_id

인덱스 이름 변경

  • 에러 발생 : ERROR 1061: Duplicate key name 'book_id_idx'
    • 여기도 마찬가지로 해당 스키마에 똑같은 이름의 index 조건이 있어서 발생한다.
  • 해결방법 : 인덱스 제약조건 이름 정해주기
    • 규칙 : 기준테이블명_참조키_idx
      ➡️ cartItems_book_id_idx

 

장바구니 담기 API 구현

  • 회원 한 명 당 장바구니 1개를 가진다.
  • user_id를 임시로 req.body에 넣어준다.
※ 고려해야 할 사항 :
로그인할 때 받은 token을 header “Authorization”에 넣고,payload값을 읽어 사용자의 id를 가져와야 한다.
➡️ 추후에 구현하기로 하고 일단 user_id는 req.body값으로 넘겨준다.
// 장바구니 담기
const addToCart = (req, res)=>{
    const {book_id, quantity, user_id} = req.body;

    let sql = `INSERT INTO cartItems (book_id, quantity, user_id) VALUES (?, ?, ?)`;
    let values = [book_id, quantity, user_id];
    // INSERT 쿼리문
    conn.query(sql, values,
        function (err, results) {
            if(err){
                console.log(err)
                return res.status(StatusCodes.BAD_REQUEST).end();   // Bad Request(400)
            }

            return res.status(StatusCodes.CREATED).json(results);  // 201
        }
    );
}

장바구니 목록 조회/삭제 API 구현

  • 도서 정보를 가져오기 위해 books 테이블과 조인해 필요한 컬럼만 가져온다. 중복 컬럼을 피하기 위해 테이블.컬럼명으로 구분
  • 삭제는 cartItems.id를 파라메터로 받아와 삭제한다.
-- 장바구니 목록 조회
   SELECT cartItems.id, book_id, title, summary, quantity, price 
     FROM cartItems 
LEFT JOIN books ON books.id = cartItems.book_id
    WHERE user_id = 1;
// 장바구니 아이템 목록 조회
const getCartItems = (req, res)=>{
    const {user_id} = req.body;
    let sql = `SELECT cartItems.id, book_id, title, summary, quantity, price
                 FROM cartItems
            LEFT JOIN books ON books.id = cartItems.book_id WHERE user_id = ?`;
    // SELECT 쿼리문
    conn.query(sql, user_id,
        (err, results) => {
            if(err){
                console.log(err)
                return res.status(StatusCodes.BAD_REQUEST).end()
            }

            res.status(StatusCodes.OK).json(results);
        }
    )
}

// 장바구니 삭제
const removeCartItem = (req, res)=>{
    // const {user_id} = req.body;
    let {id} = req.params;  // cartItems.Id
    id = parseInt(id);

    let sql = `DELETE FROM cartItems WHERE id = ?`;
    // let values = [id, user_id];
    // DELETE 쿼리문
    conn.query(sql, id,
        function (err, results) {
            if(err){
                console.log(err)
                return res.status(StatusCodes.BAD_REQUEST).end();   // Bad Request(400)
            }

            return res.status(StatusCodes.OK).json(results);  // 200
        }
    );
}

장바구니에서 선택한 상품 목록 조회

  • 장바구니에서 수량 변경 시 : db에 업데이트할 예정
  • 장바구니에서 선택한 항목들만 따로 목록 조회를 해줄 것이다.
  • 선택한 항목들의 cartItems.id를 받아와 selected 배열에 넣어준 뒤, 해당값으로 조회
  • 쿼리에선 IN 문법 활용 
-- 장바구니에서 선택한 상품 목록 조회
SELECT *
 FROM cartItems 
WHERE user_id = 1
  AND id IN (1, 3);
  • 장바구니 아이템 목록 조회와 분기처리
    • 장바구니 아이템 목록 조회와 API가 같아 합칠 수 있다.
    • 선택한 장바구니 목록 조회 시 selected 배열을 추가하여 selected 유무로 분리할 수 있다.
// 장바구니 아이템 목록 조회 / 장바구니에서 선택한 주문 예상 상품 조회
const getCartItems = (req, res)=>{
    const {user_id, selected} = req.body;   // selected = [1, 3]
    let sql = `SELECT cartItems.id, book_id, title, summary, quantity, price
                 FROM cartItems
            LEFT JOIN books ON books.id = cartItems.book_id
            WHERE user_id = ?
              AND cartItems.id IN (?)`;
    let values = [user_id, selected];
    // SELECT 쿼리문
    conn.query(sql, values,
        (err, results) => {
            if(err){
                console.log(err)
                return res.status(StatusCodes.BAD_REQUEST).end()
            }

            res.status(StatusCodes.OK).json(results);
        }
    )
}
  • 여기에서 selected 배열의 cartItems.id값을 어떻게 IN 뒤의 물음표에 넣어줄 수 있을까?
  • 다 꺼내서 IN 뒤에 물음표 갯수를 그만큼 생성해서 넣어줘야 할까?
    ➡️ 배열 통째로 values값에 넣어줘도 IN 뒤에 배열로 들어간다. (최근 업데이트된 버전 기준)

 


fk명과 index명을 정해주는 이유를 알았다. 같은 스키마에서 중복된 제약조건명과 index명이 생길 수 있다는 것을 간과했다.

SQL 쿼리문의 IN 문법 활용 시 배열을 변수값으로 받을 때 ? 안에 배열을 통째로 받을 수 있다는 것을 알게 되었다. 

여기서 궁금한 점이 sql 문을 각 API 기능마다 넣어주는 것이 과연 효율적인 코드 처리일지가 궁금하다. 스프링에서처럼 마이바티스를 활용해 해당 쿼리를 불러오는 방법은 없는지 그 부분을 연구해보고 싶다.