Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

사고쳤어요

[도서 쇼핑몰] Node.js - 주문 API 구현(mysql 비동기 처리) 본문

웹 풀스택

[도서 쇼핑몰] Node.js - 주문 API 구현(mysql 비동기 처리)

kevinmj12 2025. 3. 18. 23:39

주문하기

주문하기는 장바구니에 존재하는 아이템 목록들 중 일부를 선택하여 주문하는 기능이다.

코드 작성에 앞서 주문하기는 다음 3가지의 내용을 진행해야 한다.

 

1. deliveries 테이블에 새로운 배송 데이터(주소, 수령인, 연락처)를 추가

2. orders 테이블에 새로운 주문 데이터(대표 책 1권, 수량, 가격, 주문자, 배송번호)를 추가

3. ordered_books 테이블에 새로운 주문번호에 맞는 책의 정보들을 추가(다른 책 3권을 시켰다면 3개의 행이 추가됨)

 

const order = (req, res) => {
  const { items, delivery, totalCounts, totalPrice, userId, firstBookTitle } =
    req.body;

  let deliveryId = 0;
  let orderId = 0;

  // 먼저 deliveries 테이블에 배송 데이터 추가
  const deliverySql = `INSERT INTO deliveries (address, receiver, contact)
                  VALUES (?, ?, ?);`;

  const deliveryValues = [
    delivery.address,
    delivery.receiver,
    delivery.contact,
  ];

  conn.query(deliverySql, deliveryValues, (err, results) => {
    if (err) {
      return res.status(StatusCodes.BAD_REQUEST).json({
        msg: `Error: ${err.code}`,
      });
    }

    deliveryId = results.insertId;
  });

  // 이어서 orders 테이블에 주문 데이터 추가
  const orderSql = `INSERT INTO orders
                            (book_title, total_counts, total_price, user_id, delivery_id)
                            VALUES (?, ?, ?, ?, ?);`;
  const orderValues = [
    firstBookTitle,
    totalCounts,
    totalPrice,
    userId,
    deliveryId,
  ];
  conn.query(orderSql, orderValues, (err, results) => {
    if (err) {
      return res.status(StatusCodes.BAD_REQUEST).json({
        msg: `Error: ${err.code}`,
      });
    }
    orderId = results.insertId;
  });

  // 이어서 ordered_books 테이블에 주문의 전체 도서 데이터 추가
  const orderedBooksSql = `INSERT INTO ordered_books (order_id, book_id, counts)
                                  VALUES ?;`;
  let orderedBooksValues = [];
  items.forEach((item) => {
    orderedBooksValues.push([orderId, item.bookId, item.counts]);
  });
  conn.query(orderedBooksSql, [orderedBooksValues], (err, results) => {
    if (err) {
      return res.status(StatusCodes.BAD_REQUEST).json({
        msg: `Error: ${err.code}`,
      });
    }
  });
  // 모든 과정이 완료되었다면 OK 리턴
  return res.status(StatusCodes.OK).end();
};

그러나 위 순서대로 코드를 실행하면

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client 에러가 발생한다.

 

위 코드가 비동기적으로 실행되기 때문에 순서가 보장되지 않고 동시에 실행되기 때문이다.

 

node.js의 require(mysql2/promise)과 mysql 비동기 처리

const mysql = require("mysql2/promise");
require("dotenv").config();

const connection = async () => {
  return await mysql.createConnection({
    host: "localhost",
    user: "root",
    password: process.env.MYSQL_PASSWORD,
    database: "bookshop",
    dateStrings: true,
  });
};

module.exports = connection;

promise와 async/await를 활용하여 순서를 지키는 방법도 좋지만 먼저 node.js의 mysql모듈에는 좋은 기능이 있다.

mysql을 require()문으로 정의할 때 뒤에 /promise를 붙여준다면 Promise를 반환하게 되어 async/await을 사용할 수 있다.

따라서 connection 함수를 async로 비동기 처리한 뒤 return await를 통해 기다리도록 순서를 설정해줄 수 있다.

 

const connection = require("../mysql");

const order = async (req, res) => {
  const conn = await connection();

  const { items, delivery, totalCounts, totalPrice, userId, firstBookTitle } =
    req.body;

  let deliveryId;
  let orderId;

  // 먼저 deliveries 테이블에 배송 데이터 추가
  const deliverySql = `INSERT INTO deliveries (address, receiver, contact)
                  VALUES (?, ?, ?);`;

  const deliveryValues = [
    delivery.address,
    delivery.receiver,
    delivery.contact,
  ];

  const [deliveryResults] = await conn.execute(deliverySql, deliveryValues);
  deliveryId = deliveryResults.insertId;

  // 이어서 orders 테이블에 주문 데이터 추가
  const orderSql = `INSERT INTO orders
                            (book_title, total_counts, total_price, user_id, delivery_id)
                            VALUES (?, ?, ?, ?, ?);`;
  const orderValues = [
    firstBookTitle,
    totalCounts,
    totalPrice,
    userId,
    deliveryId,
  ];

  const [orderResults] = await conn.execute(orderSql, orderValues);
  orderId = orderResults.insertId;

  // items(장바구니 id)로부터 book_id, counts 받아옴
  const getCartItemsSql = `SELECT book_id, counts FROM cart_items WHERE id IN (?);`;
  const orderItems = await conn.query(getCartItemsSql, orderItems);

  // 이어서 ordered_books 테이블에 주문의 전체 도서 데이터 추가
  const orderedBooksSql = `INSERT INTO ordered_books (order_id, book_id, counts)
                                  VALUES ?;`;
  let orderedBooksValues = [];
  orderItems.forEach((item) => {
    orderedBooksValues.push([orderId, item.bookId, item.counts]);
  });
  const [orderedBooksReults] = await conn.query(orderedBooksSql, [
    orderedBooksValues,
  ]);

  // 이어서 장바구니의 목록 삭제
  const deleteResults = await deleteCartItems(conn, items);

  // 모든 과정이 완료되었다면 OK 리턴
  return res.status(StatusCodes.OK).json(results);
};

const deleteCartItems = async (conn, items) => {
  const sql = `DELETE FROM cart_items WHERE id IN (?);`;

  return await conn.query(sql, items);
};

deliveries 테이블
orders 테이블
ordered_books 테이블

 

주문 내역 조회

const getOrders = async (req, res) => {
  const conn = await connection();

  const sql = `SELECT orders.id, book_title, total_counts, total_price, created_at,
  address, receiver, contact
  FROM orders LEFT JOIN deliveries
  ON orders.delivery_id = deliveries.id;`;

  const [rows, fields] = await conn.query(sql);

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

 

주문 상세 조회

const getOrderDetails = async (req, res) => {
  const { id } = req.params;

  const conn = await connection();

  const sql = `SELECT book_id, book_title, author, price, counts, 
  FROM ordered_books LEFT JOIN books
  ON ordered_books.book_id = books.id 
  WHERE order_id = ?;`;

  const [rows, fields] = await conn.query(sql, id);

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