스파르타코딩클럽 웹개발 종합반

[항해4기] 스파르타코딩클럽 Node.js 기초반 4주차 회고록

kwaktaem 2021. 11. 16. 22:14

스파르타코딩클럽 Node.js 기초반

4주차 개발일지

 

 

 

 

목차

 

  1.  쇼핑몰 페이지 구현
  2.  4주차 소감

 

 

 

 

1. 쇼핑몰 페이지 구현

 

- 상품 필터링하기

  • 카테고리의 음료면 음료 음식이면 음식으로 나게 구현해줬다. 
  • HTML을 .ejs로 변환해서 만들어줬고, 라우팅을 해줬다.
  • 라우팅 후 DB를 활용하기 위해 API 작업도 해줬다.

 

 

카테고리 '음료'

음료를 DB에 담아서 호출을 해줬다.

카테고리 '음식'

음식을 DB에 담아서 호출을 해줬다.

 

- 상품 장바구니에 담기

  • 상품을 장바구니에 담기 위해서 데이터 정보를 담아서 정보를 넘겨주는 작업을 했다.
  • Schema작업도 해주고 API 작업을 해줬다.
  • 장바구니에 DB만 있으면 안되니까 .ejs를 이용해서 페이지 구현을 하면서 라우팅도 해줬다.

 

 

장바구니 Schema

const mongoose = require("mongoose");

const { Schema } = mongoose;
const cartSchema = new Schema({
  goodsId: {
    type: Number,
    required: true,
    unique: true
  },
  quantity: {
    type: Number,
    required: true
  }
});

module.exports = mongoose.model("Cart", cartSchema);

 

장바구니 Schema에 따른 API

const Cart = require("../schemas/cart");

router.post("/goods/:goodsId/cart", async (req, res) => {
  const { goodsId } = req.params;
  const { quantity } = req.body;

  isCart = await Cart.find({ goodsId });
  console.log(isCart, quantity);
  if (isCart.length) {
    await Cart.updateOne({ goodsId }, { $set: { quantity } });
  } else {
    await Cart.create({ goodsId: goodsId, quantity: quantity });
  }
  res.send({ result: "success" });
});

 

장바구니 라우팅

app.get("/cart", (req, res) => {
    res.render("cart");
});

 

장바구니 페이지 

```jsx
<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <link rel="icon" href="/static/favicon.ico" type="image/x-icon" />
    <link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon" />
    <!-- Bootstrap CSS -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
      integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2"
      crossorigin="anonymous"
    />

    <!-- Font Awesome CSS -->
    <link
      href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
      rel="stylesheet"
    />
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script
      src="https://code.jquery.com/jquery-3.5.1.js"
      integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
      integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"
      integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
      crossorigin="anonymous"
    ></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>

    <link href="/static/mystyle.css" rel="stylesheet" />
    <title>스파르타 쇼핑몰 | 장바구니</title>
    <script>
      $(document).ready(function() {
        get_goods();
        $("#categorySelect").on("change", function() {
          get_goods($(this).val());
        });
      });

      function sign_out() {
        $.removeCookie("mytoken", { path: "/" });
        $.removeCookie("userName", { path: "/" });
        window.location.href = "/";
      }

      function get_goods() {
        $("#goodsList").empty();
        $.ajax({
          type: "GET",
          url: `/api/cart`,
          data: {},
          success: function(response) {
            let goods = response["cart"];
            for (let i = 0; i < goods.length; i++) {
              make_card(goods[i]["goods"]);
            }
            getSum();
          }
        });
      }

      function order() {
        $.ajax({
          type: "GET",
          url: `/api/cart`,
          data: {},
          success: function(response) {
            let goods = response["cart"];
            let cart = [];
            for (let i = 0; i < goods.length; i++) {
              cart.push({
                goodsName: goods[i]["name"],
                quantity: goods[i]["goodsId"]
              });
            }
            sessionStorage.setItem("cart", JSON.stringify(cart));
            window.location.href = "/order";
          }
        });
      }

      function make_card(item) {
        let htmlTemp = `<div class="card mb-2">
                                    <div class="row no-gutters">
                                        <div class="col-4" style="background: #868e96;" onclick="location.href='/detail?goodsId=${
                                          item["goodsId"]
                                        }'">
                                            <img src="${item["thumbnailUrl"]}"
                                                 class="card-img-top h-100" alt="...">
                                        </div>
                                        <div class="col-8">
                                            <div class="card-body py-1">
                                                <div class="card-title row mt-2">
                                                    <p class="font-weight-bold col" style="display: inline">${
                                                      item["name"]
                                                    }</p>
                                                    <span class="card-price col text-right">$${number2decimals(
                                                      item["price"]
                                                    )}</span>

                                                </div>
                                                <div class="form-group row mr-0">
                                                    <div class="col-7 pr-2">
                                                        <button class="btn btn-outline-sparta btn-block" onclick="removeItem(${
                                                          item["goodsId"]
                                                        })">삭제</button>
                                                    </div>
                                                    <select class="custom-select col-5" id="numberSelect-${
                                                      item["goodsId"]
                                                    }">
                                                        <option selected value=1>1</option>
                                                        <option value=2>2</option>
                                                        <option value=3>3</option>
                                                    </select>
                                                </div>

                                            </div>

                                        </div>
                                    </div>
                                </div>`;
        $("#goodsList").append(htmlTemp);
        $(`#numberSelect-${item["goodsId"]}`).change(function() {
          // console.log(parseInt($(this).val()))
          $.ajax({
            type: "PATCH",
            url: `/api/goods/${item["goodsId"]}/cart`,
            data: {
              quantity: parseInt($(this).val())
            },
            success: function(response) {
              if (response["result"] == "success") {
                console.log(response["msg"]);
                getSum();
              }
            }
          });
        });
      }

      function removeItem(goodsId) {
        $.ajax({
          type: "DELETE",
          url: `/api/goods/${goodsId}/cart`,
          data: {},
          success: function(response) {
            if (response["result"] == "success") {
              get_goods();
            }
          }
        });
      }

      function getSum() {
        let $prices = $(".card-price");
        let $selects = $("select");
        if ($prices.length != $selects.length) {
          console.log("길이가 맞지 않습니다.");
          return;
        }
        let sum = 0;
        for (let i = 0; i < $prices.length; i++) {
          let price = parseFloat(
            $($prices[i])
              .text()
              .replace("$", "")
          );
          let count = parseInt($($selects[i]).val());
          // console.log(price, count)
          sum += price * count;
        }
        $("#priceSum").text("$" + number2decimals(sum));
        sessionStorage.setItem("priceSum", number2decimals(sum));
      }

      function number2decimals(num) {
        return (Math.round(num * 100) / 100).toFixed(2);
      }
    </script>
    <style>
      .card {
        cursor: pointer;
      }
    </style>
  </head>

  <body>
    <nav
      class="navbar navbar-expand-sm navbar-dark bg-sparta justify-content-end"
    >
      <a class="navbar-brand" href="/goods">
        <img
          src="/static/logo_big_tr.png"
          width="30"
          height="30"
          class="d-inline-block align-top"
          alt=""
        />
        스파르타 쇼핑몰
      </a>
      <button
        class="navbar-toggler ml-auto"
        type="button"
        data-toggle="collapse"
        data-target="#navbarSupportedContent"
        aria-controls="navbarSupportedContent"
        aria-expanded="true"
        aria-label="Toggle navigation"
      >
        <span class="navbar-toggler-icon"></span>
      </button>
      <div
        class="navbar-collapse collapse flex-grow-0 ml-auto"
        id="navbarSupportedContent"
        style=""
      >
        <ul class="navbar-nav mr-auto text-right">
          <li class="nav-item" id="link-cart">
            <a class="nav-link" href="/cart">
              장바구니<i
                class="fa fa-shopping-cart ml-2"
                aria-hidden="true"
              ></i>
            </a>
          </li>
          <li class="nav-item" id="link-logout">
            <a class="nav-link" data-toggle="modal" data-target="#signOutModal">
              로그아웃<i class="fa fa-sign-out ml-2" aria-hidden="true"></i>
            </a>
            <div
              class="modal text-left"
              id="signOutModal"
              tabindex="-1"
              role="dialog"
              aria-labelledby="signOutModalLabel"
              aria-hidden="true"
            >
              <div class="modal-dialog" role="document">
                <div class="modal-content">
                  <div class="modal-header">
                    <h5 class="modal-title" id="signOutModalLabel">로그아웃</h5>
                    <button
                      type="button"
                      class="close"
                      data-dismiss="modal"
                      aria-label="Close"
                    >
                      <span aria-hidden="true">&times;</span>
                    </button>
                  </div>
                  <div class="modal-body">
                    로그아웃하시면 장바구니가 사라져요!
                  </div>
                  <div class="modal-footer">
                    <button
                      type="button"
                      class="btn btn-outline-sparta"
                      data-dismiss="modal"
                    >
                      취소
                    </button>
                    <button
                      type="button"
                      class="btn btn-sparta"
                      onclick="sign_out()"
                    >
                      로그아웃하기
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </li>
        </ul>
      </div>
    </nav>
    <div class="wrap">
      <div class="mb-3">
        <h4>
          <i class="fa fa-shopping-cart mr-1" aria-hidden="true"></i>장바구니
        </h4>
      </div>
      <div id="goodsList">
        <div class="card mb-2" onclick="location.href='#'">
          <div class="row no-gutters">
            <div class="col-4" style="background: #868e96;">
              <img
                src="https://cdn.pixabay.com/photo/2016/09/07/19/54/wines-1652455_1280.jpg"
                class="card-img-top h-100"
                alt="..."
              />
            </div>
            <div class="col-8">
              <div class="card-body py-1">
                <div class="card-title row mt-2">
                  <p class="font-weight-bold col" style="display: inline">
                    상품 1
                  </p>
                  <span class="card-price col text-right">$6.20</span>
                </div>
                <div class="form-group row mr-0">
                  <div class="col-7 pr-2">
                    <button class="btn btn-outline-sparta btn-block">
                      삭제
                    </button>
                  </div>
                  <!--                                    <label for="numberSelect" class="col col-form-label">수량</label>-->
                  <select class="custom-select col-5" id="numberSelect">
                    <option selected value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                  </select>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <hr />

      <div class="row mb-3">
        <div class="col-5">총 상품금액</div>
        <div class="col-7 text-right" id="priceSum">$6.20</div>
      </div>
      <button type="button" class="btn btn-sparta btn-block" onclick="order()">
        구매
      </button>
    </div>
  </body>
</html>
```

 

장바구니 페이지 API

router.get("/cart", async (req, res) => {
  const cart = await Cart.find({});
  const goodsId = cart.map(cart => cart.goodsId);

  goodsInCart = await Goods.find()
    .where("goodsId")
    .in(goodsId);

  concatCart = cart.map(c => {
    for (let i = 0; i < goodsInCart.length; i++) {
      if (goodsInCart[i].goodsId == c.goodsId) {
        return { quantity: c.quantity, goods: goodsInCart[i] };
      }
    }
  });

  res.json({
    cart: concatCart
  });
});

 

장바구니 기능을 구현해서 장바구니 페이지 안에 상품을 추가 시킨 사진이다.

 

 

- 장바구니 아이템 삭제하기

  • 장바구니에 추가하는 기능을 구현해냈으니, 반대로 삭제하는 기능을 구현해서 만약 실제 고객이 쓰는 것이라 보면 실수로 장바구니에 담을 수도 있으니까 삭제 기능도 구현을 해줬다.
  • 장바구니 추가 했던 파일에 삭제할 수 있는 기능을 구현하기 위해서 파일을 수정해줬다. 

 

장바구니 아이템 삭제 기능 추가

router.delete("/goods/:goodsId/cart", async (req, res) => {
  const { goodsId } = req.params;

  const isGoodsInCart = await Cart.find({ goodsId });
  if (isGoodsInCart.length > 0) {
    await Cart.deleteOne({ goodsId });
  }

  res.send({ result: "success" });
});

 

장바구니 아이템을 삭제하는 기능을 구현한 사진이다.

 

- 상품 수량 수정하기

  • 장바구니에서 상품 수량이 원하는 만큼 구현이 안됐어서 그 기능을 추가해줬다.
  • 상품 수량을 적어주기 위해서 장바구니 페이지 만든 파일을 수정을 해줬다.
  • 수량을 변경해주는 API를 추가해줬다.

 

장바구니 상품 수량을 변경해주는 API

router.patch("/goods/:goodsId/cart", async (req, res) => {
  const { goodsId } = req.params;
  const { quantity } = req.body;

  isCart = await Cart.find({ goodsId });
  console.log(isCart, quantity);
  if (isCart.length) {
    await Cart.updateOne({ goodsId }, { $set: { quantity } });
  }

  res.send({ result: "success" });
})

 

장바구니 상품의 수량을 수정할 수 있도록 기능을 구현해준 사진이다.

 

- 구매 페이지 추가하기

  • 마지막으로 장바구니에 상품을 담거나 바로 구매를 하기 위해서 구매 페이지를 구현했다.
  • 구매 페이지가 추가되어서 라우트를 추가해줬다.

 

쇼핑몰 구매 페이지

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <link rel="icon" href="/favicon.ico" type="image/x-icon" />
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
    <!-- Bootstrap CSS -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
      integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2"
      crossorigin="anonymous"
    />

    <!-- Font Awesome CSS -->
    <link
      href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
      rel="stylesheet"
    />
    <link href="/static/mystyle.css" rel="stylesheet" />
    <title>스파르타 쇼핑몰 | 주문</title>

    <style>
      .card {
        cursor: pointer;
      }
    </style>
  </head>

  <body>
    <nav
      class="navbar navbar-expand-sm navbar-dark bg-sparta justify-content-end"
    >
      <a class="navbar-brand" href="/goods.html">
        <img
          src="/static/logo_big_tr.png"
          width="30"
          height="30"
          class="d-inline-block align-top"
          alt=""
        />
        스파르타 쇼핑몰
      </a>
      <button
        class="navbar-toggler ml-auto"
        type="button"
        data-toggle="collapse"
        data-target="#navbarSupportedContent"
        aria-controls="navbarSupportedContent"
        aria-expanded="true"
        aria-label="Toggle navigation"
      >
        <span class="navbar-toggler-icon"></span>
      </button>
      <div
        class="navbar-collapse collapse flex-grow-0 ml-auto"
        id="navbarSupportedContent"
      >
        <ul class="navbar-nav mr-auto text-right">
          <li class="nav-item" id="link-cart">
            <a class="nav-link" href="/cart.html">
              장바구니<i
                class="fa fa-shopping-cart ml-2"
                aria-hidden="true"
              ></i>
            </a>
          </li>
          <li class="nav-item" id="link-logout">
            <a class="nav-link" data-toggle="modal" data-target="#signOutModal">
              로그아웃<i class="fa fa-sign-out ml-2" aria-hidden="true"></i>
            </a>
            <div
              class="modal text-left"
              id="signOutModal"
              tabindex="-1"
              role="dialog"
              aria-labelledby="signOutModalLabel"
              aria-hidden="true"
            >
              <div class="modal-dialog" role="document">
                <div class="modal-content">
                  <div class="modal-header">
                    <h5 class="modal-title" id="signOutModalLabel">로그아웃</h5>
                    <button
                      type="button"
                      class="close"
                      data-dismiss="modal"
                      aria-label="Close"
                    >
                      <span aria-hidden="true">&times;</span>
                    </button>
                  </div>
                  <div class="modal-body">정말로 로그아웃 하시겠습니까?</div>
                  <div class="modal-footer">
                    <button
                      type="button"
                      class="btn btn-outline-sparta"
                      data-dismiss="modal"
                    >
                      취소
                    </button>
                    <button
                      type="button"
                      class="btn btn-sparta"
                      onclick="signOut()"
                    >
                      로그아웃하기
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </li>
        </ul>
      </div>
    </nav>
    <div class="wrap">
      <div class="mb-3">
        <h4><i class="fa fa-truck mr-1" aria-hidden="true"></i>배송지 입력</h4>
      </div>
      <div>
        <div class="form-group">
          <label for="exampleInputName">받는 사람</label>
          <input
            type="text"
            class="form-control"
            id="exampleInputName"
            placeholder="홍길동"
          />
        </div>

        <div class="form-group">
          <label for="exampleInputMobile">연락처</label>
          <input
            type="text"
            class="form-control"
            id="exampleInputMobile"
            placeholder="010-xxx-xxxx"
          />
        </div>

        <div class="form-group">
          <label for="exampleInputAddress1">주소</label>
          <input
            type="text"
            class="form-control"
            id="exampleInputAddress1"
            placeholder="도로명, 건물명, 번지 검색"
          />
          <input
            type="text"
            class="form-control mt-2"
            id="exampleInputAddress2"
            placeholder="상세주소"
          />
        </div>

        <button
          type="button"
          class="btn btn-sparta btn-block"
          data-toggle="modal"
          data-target="#orderModal"
          id="btnOrder"
        >
          $0.20 결제
        </button>
        <div
          class="modal text-left"
          id="orderModal"
          tabindex="-1"
          role="dialog"
          aria-labelledby="orderModalLabel"
          aria-hidden="true"
        >
          <div class="modal-dialog" role="document">
            <div class="modal-content">
              <div class="modal-header">
                <h5 class="modal-title" id="orderModalLabel">결제완료</h5>
                <button
                  type="button"
                  class="close"
                  data-dismiss="modal"
                  aria-label="Close"
                >
                  <span aria-hidden="true">&times;</span>
                </button>
              </div>
              <div class="modal-body">
                <span id="nickname"></span>님, 주문해주셔서 감사합니다 :)
              </div>
              <div class="modal-footer">
                <button
                  type="button"
                  class="btn btn-outline-sparta btn-confirm"
                  data-dismiss="modal"
                >
                  확인
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script
      src="https://code.jquery.com/jquery-3.5.1.js"
      integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
      integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"
      integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
      crossorigin="anonymous"
    ></script>
    <script src="https://cdn.socket.io/socket.io-3.0.1.min.js"></script>

    <script>
      $(document).ready(function () {
        getSelf(function (user) {
          const order = JSON.parse(sessionStorage.getItem("ordered"));
          $("#nickname").text(user.nickname);
          $("#btnOrder")
            .text(
              `$${number2decimals(
                order.reduce(
                  (s, o) => s + Number(o.goods.price) * o.quantity,
                  0
                )
              )} 결제`
            )
            .click(function () {
              postOrder(user, order);
            });
          $(".btn-confirm").click(function () {
            location.href = "/goods.html";
          });
        });
      });

      function number2decimals(num) {
        return (Math.round(num * 100) / 100).toFixed(2);
      }
    </script>
  </body>
</html>

 

 

구매 페이지 구현

 

2. 4주차 소감

 

4주차에 들어와서는 쇼핑몰 만들기를 구현해봤다. 많이 부족한 것 같은데 벌써 5주차 하나 남았다... 처음엔 이게 뭐고 이게 뭘까해서 이해가 안갔지만 직접 코드를 써내려가면서 머릿속에 넣어지면서 기초는 알아가는 것 같다. 내 스스로 한 것은 아니고 강의 영상을 보면서 따라한거지만 그래도 할 줄 모르던 Node.js를 해봤다는게 의의를 두고 싶다... 남은 5주차 강의를 듣고 항해99 3주차 프로젝트를 하러가야겠다 ㅜㅜ