<장바구니 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
- 규칙 : fk_기준 테이블명_참조테이블명_참조키
인덱스 이름 변경
- 에러 발생 : ERROR 1061: Duplicate key name 'book_id_idx'
- 여기도 마찬가지로 해당 스키마에 똑같은 이름의 index 조건이 있어서 발생한다.
- 해결방법 : 인덱스 제약조건 이름 정해주기
- 규칙 : 기준테이블명_참조키_idx
➡️ cartItems_book_id_idx
- 규칙 : 기준테이블명_참조키_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 기능마다 넣어주는 것이 과연 효율적인 코드 처리일지가 궁금하다. 스프링에서처럼 마이바티스를 활용해 해당 쿼리를 불러오는 방법은 없는지 그 부분을 연구해보고 싶다.
'프로젝트 > 도서구매사이트' 카테고리의 다른 글
| 1027 비동기 처리 방식 종류 - promise, async/await (0) | 2025.10.26 |
|---|---|
| 1024 주문 API 구현 - insertId 활용, 다중 인서트 구현 (0) | 2025.10.23 |
| 1022 좋아요 API 구현 / 좋아요 개수 출력 - COUNT(), AS, 서브쿼리 (0) | 2025.10.21 |
| 1021 SQL 시간 범위 구하기(신간 안내), 도서 목록 페이지 구현 (0) | 2025.10.20 |
| 1020 도서, 카테고리 API 구현 (0) | 2025.10.17 |