스파르타코딩클럽 웹개발 종합반
[항해4기] 스파르타코딩클럽 Node.js 기초반 4주차 회고록
kwaktaem
2021. 11. 16. 22:14
스파르타코딩클럽 Node.js 기초반
4주차 개발일지
목차
- 쇼핑몰 페이지 구현
- 4주차 소감
1. 쇼핑몰 페이지 구현
- 상품 필터링하기
- 카테고리의 음료면 음료 음식이면 음식으로 나게 구현해줬다.
- HTML을 .ejs로 변환해서 만들어줬고, 라우팅을 해줬다.
- 라우팅 후 DB를 활용하기 위해 API 작업도 해줬다.
카테고리 '음료'
카테고리 '음식'
- 상품 장바구니에 담기
- 상품을 장바구니에 담기 위해서 데이터 정보를 담아서 정보를 넘겨주는 작업을 했다.
- 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">×</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">×</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">×</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주차 프로젝트를 하러가야겠다 ㅜㅜ