

위와 같이 디비를 설계를 했을때 누가 좋아요를 눌렀는지 어떻게 알 수 있을까??
=> 로그인을 한 상태여야 조회가 가능하고 토큰에서 꺼내서 확인할 수 있다.


const addLike = (req, res) => {
//카테고리 전체 목록 리스트
console.log("1");
const { id } = req.params;
const { user_id } = req.body;
let sql = `INSERT INTO likes (user_id, liked_book_id) VALUES (?, ?)`;
let values = [user_id, id];
conn.query(sql, values, (err, results) => {
if (err) {
//서버측 에러
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
res.status(StatusCodes.OK).json(results);
});
};
const removeLike = (req, res) => {
//좋아요 취소
const { id } = req.params; //book_id
const { user_id } = req.body;
let sql = `DELETE FROM likes WHERE user_id = ? AND liked_book_id = ?`;
let values = [user_id, id];
conn.query(sql, values, (err, results) => {
if (err) {
//서버측 에러
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
res.status(StatusCodes.OK).json(results);
});
};
count는 데이터의 개수를 반환해주는 함수
SELECT count(*) FROM likes WHERE liked_book_id = 1
=> liked_book_id가 1인 데이터의 개수
books 테이블에 있는 likes라는 컬럼은 이 책에 좋아요를 누른 사람이 몇명인지를 나타내는 컬럼인데 이 컬럼을 추가하는 쿼리문은 다음과 같다.

SELECT *, (SELECT count(*) FROM likes WHERE liked_book_id=books.id) AS likes FROM books;
개별 도서 조회 시, 사용자가 좋아요를 했는지에 대한 여부를 포함시켜보기 (EXIST 사용 => 조건에 맞는 것이 있는지 없는지)
const conn = require("../mariadb");
const { StatusCodes } = require("http-status-codes");
const allBooks = (req, res) => {
let { category_id, news, limit, currentPage } = req.query;
console.log(limit, currentPage);
let offset = limit * (currentPage - 1);
let sql = `SELECT *, (SELECT count(*) FROM likes WHERE books.id=liked_book_id) AS likes FROM books`; //좋아요 개수가 포함된 books테이블 조회
let values = [];
if (category_id && news) {
// news가 true이면서 category_id가 있는 경우
sql += ` WHERE category_id=? AND pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()`; // 조건문 수정
values = [parseInt(category_id)]; // 값 추가
} else if (category_id) {
// category_id만 있는 경우
sql += ` WHERE category_id=?`;
values = [parseInt(category_id)]; // 값 추가
} else if (news) {
// news만 있는 경우
sql += ` WHERE pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()`;
}
sql += ` LIMIT ? OFFSET ?`; // LIMIT과 OFFSET 추가
values.push(parseInt(limit), parseInt(offset)); // 값 추가
conn.query(sql, values, (err, results) => {
// 쿼리 실행
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
if (results.length) {
return res.status(StatusCodes.OK).json(results);
} else {
return res.status(StatusCodes.NOT_FOUND).end();
}
});
};
const bookDetail = (req, res) => {
let book_id = req.params.id;
let { user_id } = req.body;
const sql = `SELECT *, (SELECT count(*) FROM likes WHERE liked_book_id=books.id) AS likes,
(SELECT EXISTS (SELECT * FROM likes WHERE user_id=? AND liked_book_id=?)) AS liked
FROM books
LEFT JOIN category ON books.category_id = category.category_id
WHERE books.id=?`;
let values = [user_id, book_id, book_id];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
if (results.length) {
return res.status(StatusCodes.OK).json(results[0]);
} else {
return res.status(StatusCodes.NOT_FOUND).end();
}
});
};
module.exports = {
allBooks,
bookDetail,
};

fk 제약조건을 걸 때, 다른 테이블에 이미 정해져 있는 제약조건과 이름이 같으면 안되기 때문에 모두 구분이 될 수 있게 이름을 지어야 한다.
fk 제약조건 이름짓기
=> fk기준 테이블명참조테이블명_참조키 ex) fk_cartItems_users_id
장바구니에 담으려면 지금 회원이 누군지를 알아야 한다. => jwt가 필요 (payload값에서 id가 필요) 또한 도서 id와 그 도서를 몇 권 담을건지 개수(quantity)도 필요하다.
CartController.js
const conn = require("../mariadb"); //db모듈
const { StatusCodes } = require("http-status-codes");
//장바구니 담기
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];
conn.query(sql, values, (err, results) => {
if (err) {
//서버측 에러
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
res.status(StatusCodes.OK).json(results);
});
};

장바구니에 담았을 때는 books 테이블에 있는 cartItems의 id, 책의 id, title, summary, quantity, price 순으로 나와야 하기 때문에, books와 JOIN 연산을 해주어야 한다.
//장바구니 아이템 목록 조회
const getCartItem = (req, res) => {
const { user_id } = req.body;
const sql = `SELECT cartItems.id, book_id, title, summary, quantity, price FROM cartItems LEFT JOIN books ON books.id = cartItems.book_id WHERE user_id = ? `;
conn.query(sql, user_id, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
결과는 아래와 같은 형식으로 출력된다.

장바구니 도서 삭제
const removeCartItem = (req, res) => {
const { id } = req.params; //cartItemId
let sql = `DELETE FROM cartItems WHERE id = ?`;
conn.query(sql, id, (err, results) => {
if (err) {
//서버측 에러
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
res.status(StatusCodes.OK).json(results);
});
};
만약 장바구니에서 도서 수량이 변경되면 (DB UPDATE cartItems) 페이지를 나갔다가 들어와도 변경된 상태로 저장하는 방식을 사용할 것이다.

req.body에는 내가 주문 페이지에 나오는 장바구니 상품들 중에 체크한 상품의 cartItemsId를 selected라는 이름의 배열로 받을 것이다.
//장바구니 아이템 목록 조회
const getCartItem = (req, res) => {
const { user_id, selected } = req.body; //selected는 장바구니에서 내가 선택한 도서들
const 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];
conn.query(sql, values, (err, results) => {
if (err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};



위의 그림을 보면 orders.delivery_id가 delivery.id를 참조한다.
또한 orderedBook.order_id가 orders.id를 참조한다.
또한 orderedBook.book_id는 books.id를 참조한다.
또한 orderedBook.user_id는 users.id를 참조한다.



회원이 누구인지 알아야 하기 떄문에 jwt가 필요하나 일단 임시로 user_id를 req.body에 넣어서 진행한다.
또한 req.body에 담겨오는 items배열은 orderedBook 테이블에 들어가야하고, delivery로 넘어오는 배송 정보는 delivery 테이블에 삽입되어야 하며, totalQuantity, totalPrice, userId는 orders 테이블에 삽입되어야 한다.
주문하기 화면에서 배송정보를 먼저 입력하고 그 아래에서 주문 정보를 입력하기 떄문에, 위에서 작성한 배송정보에 대한 데이터의 PK를 바로 가져와야 하는데, 이것을 가져오는 방법은 크게 두 가지가 있다. 또, 주문을 하면 orderedBook 테이블에 데이터가 삽입되어야 하는데, 이것은 orders테이블의 가장 최신값을 가져와야 한다.

//[2기] 이현철
const conn = require("../mariadb");
const { StatusCodes } = require("http-status-codes");
const order = (req, res) => {
const {
items,
delivery,
totalQuantity,
totalPrice,
userId,
firstBookTitle,
} = req.body;
let delivery_id = 3;
let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
let values = [delivery.address, delivery.receiver, delivery.contact];
// conn.query(sql, values, (err, results) => {
// if (err) {
// //서버측 에러
// console.log(err);
// return res.status(StatusCodes.BAD_REQUEST).end();
// }
// delivery_id = 3; //results.insertId; //가장 최근에 들어간 데이터의 id
// return res.status(StatusCodes.OK).json(results);
// });
sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
VALUES (?,?,?,?,?)`;
values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
// firstBookTitle : 대표책제목
conn.query(sql, values, (err, results) => {
if (err) {
//서버측 에러
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
order_id = results.insertId;
console.log(order_id);
return res.status(StatusCodes.OK).json(results);
});
};
const getOrders = (req, res) => {
res.json({
message: "주문 내역 조회",
});
};
const getOrderDetail = (req, res) => {
res.json({
message: "주문 상세 조회",
});
};
module.exports = {
order,
getOrderDetail,
getOrders,
};
const order = (req, res) => {
const {
items,
delivery,
totalQuantity,
totalPrice,
userId,
firstBookTitle,
} = req.body;
let delivery_id = 3;
let order_id = 2;
let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
let values = [delivery.address, delivery.receiver, delivery.contact];
// conn.query(sql, values, (err, results) => {
// if (err) {
// //서버측 에러
// console.log(err);
// return res.status(StatusCodes.BAD_REQUEST).end();
// }
// delivery_id = 3; //results.insertId; //가장 최근에 들어간 데이터의 id
// return res.status(StatusCodes.OK).json(results);
// });
sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
VALUES (?,?,?,?,?)`;
values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
// firstBookTitle : 대표책제목
// conn.query(sql, values, (err, results) => {
// if (err) {
// //서버측 에러
// console.log(err);
// return res.status(StatusCodes.BAD_REQUEST).end();
// }
// order_id = results.insertId;
// console.log(order_id);
// return res.status(StatusCodes.OK).json(results);
// });
sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
//items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
values = [];
items.forEach((item) => {
values.push([order_id, item.book_id, item.quantity]);
console.log(values);
});
conn.query(sql, [values], (err, results) => {
if (err) {
//서버측 에러
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
//[2기] 이현철
const conn = require("../mariadb");
const { StatusCodes } = require("http-status-codes");
const order = (req, res) => {
const {
items,
delivery,
totalQuantity,
totalPrice,
userId,
firstBookTitle,
} = req.body;
let delivery_id;
let order_id;
let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
let values = [delivery.address, delivery.receiver, delivery.contact];
conn.query(sql, values, (err, results) => {
if (err) {
//서버측 에러
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
delivery_id = results.insertId; //가장 최근에 들어간 데이터의 id
console.log(delivery_id);
console.log(results.insertId);
});
console.log(delivery_id);
sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
VALUES (?,?,?,?,?)`;
values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
// firstBookTitle : 대표책제목
conn.query(sql, values, (err, results) => {
if (err) {
//서버측 에러
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
order_id = results.insertId;
});
sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
//items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
values = [];
items.forEach((item) => {
values.push([order_id, item.book_id, item.quantity]);
});
conn.query(sql, [values], (err, results) => {
if (err) {
//서버측 에러
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
});
};
const getOrders = (req, res) => {
res.json({
message: "주문 내역 조회",
});
};
const getOrderDetail = (req, res) => {
res.json({
message: "주문 상세 조회",
});
};
module.exports = {
order,
getOrderDetail,
getOrders,
};
위와 같이 코드를 작성해보면 아래 사진에 대한 부분에서 콘솔이 밖에 있는 로그가 먼저 찍히고 나서 안에 있는 로그가 찍히는 일이 발생하여 코드에서 오류가 발생한다.


Node.js 비동기 처리 방식

위의 사진들을 보면 Node.js의 비동기 처리 방식에 대한 내용인데, 우리의 코드가 에러가 발생하는 이유를 잘 보여준다.
왜냐하면 우리의 코드를 잘 보면 delivery 테이블을 먼저 채우고 거기서 나온 delivery_id를 가지고 orders 테이블을 채우고 또 거기서 나온 데이터를 사용하여 orderedBooks 테이블을 채우기 때문에 총 쿼리문이 3개인데 Node에서는 쿼리문이 나오게 된다면 기다려주지 않고 바로 다음 코드를 실행하기 때문에 delivery 테이블에는 데이터가 잘 들어가지만 그 다음인 orders 테이블에 데이터를 삽입할 때 delivery_id가 아직 넘어오기 전에 쿼리문이 실행됐기 때문에 오류가 발생하는 것이다.
Promise가 무엇인가?
프로미스는 자바스크립트 비동기 처리에 사용되는 객체이다. 여기서 자바스크립트의 비동기 처리란 "특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성"을 의마한다.
Promise가 왜 필요한가?
프로미스는 주로 서버에서 받아온 데이터를 화면에 표시할 때 사용한다. 일반적으로 웹 애플리케이션을 구현할 때 서버에서 데이터를 요청하고 받아오기 위해 API를 사용할 때 데이터를 받아오기도 전에 마치 데이터를 다 받아온 것 마냥 화면에 데이터를 표시하려고 하면 오류가 나거나 빈 화면이 뜨기 때문에 사용하는 것이 바로 Promise이다.

// async-await : Promise 객체를 좀 더 쉽고 편하게 사용하는 문법
//즉 비동기 처리가 쉽다.
//async 함수
// ____ function f() {} : 일반 함수
// async function f() {} : async 함수
async function f() {
return 7;
// async 함수는 문조건 프로미스 객체를 반환한다.
// 만약 반환값이 프로미스가 아니면, 프로미스로 감싼다.
}
f().then(
function(result){
console.log("promiser resolve : ", result);
}
}
//async의 두번째 기능
//Promise 객체가 일이 끝날 때까지 기다릴 수 있는
//공간을 제공한다.
//await은 async함수 안에서만 사용가능
async function f() {
let promise = new Promise(function(resolve, reject){
setTimeout(() => resolve("완료!", 3000);
});
let result = await promise; //promise객체가 일 다 할때까지 기다려
console.log(result); //3초 뒤 완료! 출력
}
//async의 두번째 기능
//Promise 객체가 일이 끝날 때까지 기다릴 수 있는
//공간을 제공한다.
//await은 async함수 안에서만 사용가능
async function f() {
//promise 객체 하나당 쿼리 하나라고 생각하면 된다.
let promise1 = new Promise(function(resolve, reject){
setTimeout(() => resolve("첫번째 쿼리", 3000);
});
let result = await promise1; //promise1객체가 일 다 할때까지 기다려
let promise2 = new Promise(function(resolve, reject){
setTimeout(() => resolve("두번째 쿼리 with" + result1, 3000);
});
let result2 = await promise2;
let promise3 = new Promise(function(resolve, reject){
setTimeout(() => resolve("세번째 쿼리 with" + result2 , 3000);
});
let result2 = await promise3;
}
//결과 (3초마다)
//첫번째 쿼리
//두번째 쿼리 with 첫번째 쿼리
//세번째 쿼리 with 두번째 쿼리 with 첫번째 쿼리
const order = async (req, res) => {
const conn = await mariadb.createConnection({
host: "localhost",
user: "root",
database: "Bookshop",
password: "root1234",
dateStrings: true,
});
const {
items,
delivery,
totalQuantity,
totalPrice,
userId,
firstBookTitle,
} = req.body;
// delievery 테이블 삽입
let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
let values = [delivery.address, delivery.receiver, delivery.contact];
let [results] = await conn.execute(sql, values);
console.log(results);
let delivery_id = results.insertId;
//orders 테이블에 삽입하는 코드
sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
VALUES (?,?,?,?,?)`;
values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
// firstBookTitle : 대표책제목
[results] = await conn.execute(sql, values); //이 코드가 끝날때까지 밑의 코드가 진행되면 안되기 때문에 await
let order_id = results.insertId;
//orderedBook 삽입하는 코드
sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
//items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
values = [];
items.forEach((item) => {
values.push([order_id, item.book_id, item.quantity]);
});
[results] = await conn.query(sql, [values]);
return res.status(StatusCodes.OK).json(results);
};
위에서 conn.query대신 conn.execute를 사용한다.. 이유는 아직 모름..
해결법 )
SET FOREIGN_KEY_CHECKS = 0
위의 코드는 우리 지금은 체크하지 말자! 라는 뜻
그 후 truncate를 해주고 다시 1로 세팅해주면 된다.
const order = async (req, res) => {
const conn = await mariadb.createConnection({
host: "localhost",
user: "root",
database: "Bookshop",
password: "root1234",
dateStrings: true,
});
const {
items,
delivery,
totalQuantity,
totalPrice,
userId,
firstBookTitle,
} = req.body;
// delievery 테이블 삽입
let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
let values = [delivery.address, delivery.receiver, delivery.contact];
let [results] = await conn.execute(sql, values); //반환을 배열형태로 한다.
console.log(results);
let delivery_id = results.insertId;
//orders 테이블에 삽입하는 코드
sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
VALUES (?,?,?,?,?)`;
values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
// firstBookTitle : 대표책제목
[results] = await conn.execute(sql, values); //이 코드가 끝날때까지 밑의 코드가 진행되면 안되기 때문에 await
let order_id = results.insertId;
//orderedBook 삽입하는 코드
sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
//items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
values = [];
items.forEach((item) => {
values.push([order_id, item.book_id, item.quantity]);
});
results = await conn.query(sql, [values]);
return res.status(StatusCodes.OK).json(results[0]);
};
response)

장바구니에서 체크한 책들을 기반으로 넘어오기 때문에 cartItemId를 갖고있다.
우리가 이전에 구현하지 않은 장바구니에서 주문한 상품 삭제하는 기능은 이 cartItemId를 사용할 것이다.
cartItems)
orderedBook)
위의 두 사진을 보면 장바구니에 ascript
book_id가 2, 3, 4, 8번 책이 들어있는 것을 확인할 수 있다. 근데 orderedBook테이블을 보면 3, 4, 8번 책을 주문했다고 나와있다.
그러면 우리가 지금 해야할 것은 cartItems 테이블에 book_id가 2인 책만 두고 삭제하는 기능을 구현해야 한다.
완성된 주문하기 API
const order = async (req, res) => {
const conn = await mariadb.createConnection({
host: "localhost",
user: "root",
database: "Bookshop",
password: "root1234",
dateStrings: true,
});
const {
items,
delivery,
totalQuantity,
totalPrice,
userId,
firstBookTitle,
} = req.body;
// delievery 테이블 삽입
let sql = `INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)`;
let values = [delivery.address, delivery.receiver, delivery.contact];
let [results] = await conn.execute(sql, values); //반환을 배열형태로 한다.
console.log(results);
let delivery_id = results.insertId;
//orders 테이블에 삽입하는 코드
sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
VALUES (?,?,?,?,?)`;
values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
// firstBookTitle : 대표책제목
[results] = await conn.execute(sql, values); //이 코드가 끝날때까지 밑의 코드가 진행되면 안되기 때문에 await
let order_id = results.insertId;
//items를 가지고 장바구니에서 book_id와 quantity를 조회
sql = `SELECT book_id, quantity FROM cartItems WHERE id IN (?)`;
let [orderItems, fields] = await conn.query(sql, [items]);
//orderedBook 삽입하는 코드
sql = `INSERT INTO orderedBook (order_id, book_id, quantity) VALUES ?;`;
//items.. 는 배열이고 이걸 하나씩 꺼내서 (forEach문을 돌려야 한다.)
values = [];
orderItems.forEach((item) => {
values.push([order_id, item.book_id, item.quantity]);
});
results = await conn.query(sql, [values]);
let result = await deleteCartItems(conn, items);
return res.status(StatusCodes.OK).json(result);
};
req에는 회원인증에 필요한 jwt가 들어오지만 우리는 아직 구현하지 않고 일단 user_id로 구현해보겠다.
const getOrders = async (req, res) => {
const conn = await mariadb.createConnection({
host: "localhost",
user: "root",
database: "Bookshop",
password: "root1234",
dateStrings: true,
});
sql = `SELECT orders.id, created_at, address, receiver, contact, book_title, total_quantity, total_price
FROM orders LEFT JOIN delivery ON orders.delivery_id = delivery.id`;
let [rows, fields] = await conn.query(sql);
return res.status(StatusCodes.OK).json(rows);
};
const getOrderDetail = async (req, res) => {
const { id } = req.params;
const conn = await mariadb.createConnection({
host: "localhost",
user: "root",
database: "Bookshop",
password: "root1234",
dateStrings: true,
});
sql = `SELECT book_id, title, author, price, quantity
FROM orderedBook LEFT JOIN books ON orderedBook.book_id = books.id
WHERE order_id = ?`;
let [rows, fields] = await conn.query(sql, [id]);
return res.status(StatusCodes.OK).json(rows);
};