-
[항해4기] 스파르타코딩클럽 Node.js 기초반 2주차 회고록스파르타코딩클럽 Node.js 기초반 2021. 11. 15. 17:16
스파르타코딩클럽 Node.js 기초반
2주차 개발일지
목차
- Express의 Routing에 대해서 알아보기
- Express의 Middleware에 대해서 알아보기
- 템플릿 엔진에 대해서 알아보고 실제 페이지를 만들어보기
- 2주차 숙제
- 2주차 소감
1. Express의 Routing에 대해서 알아보기
라우팅은 URI 및 특정한 HTTP 요청 메소드인 특정 엔드포인트에 대한 클라이언트 요청에 애플리케이션이 응답하는 방법을 결정하는 것을 말한다. 각 라우트는 하나 이상의 핸들러 함수를 가질 수 있으며, 이러한 함수는 라우트가 일치할 때 실행된다.
app.METHOD(PATH, HANDLER)
- app은 express의 인스턴스다.
- METHOD는 HTTP 요청 메소드다.
- PATH는 서버에서의 경로이다.
- HANDLER는 라우트가 일치할 때 실행되는 함수다.
홈 페이지에서 Hello World!로 응답:
app.get('/', function (req, res) { res.send('Hello World!'); });
애플리케이션의 홈 페이지인 루트 라우트(/)에서 POST 요청에 응답:
app.post('/', function (req, res) { res.send('Got a POST request'); });
/user 라우트에 대한 PUT 요청에 응답:
app.put('/user', function (req, res) { res.send('Got a PUT request at /user'); });
/user 라우트에 대한 DELETE 요청에 응답:
app.delete('/user', function (req, res) { res.send('Got a DELETE request at /user'); });
위 내용은 Express 홈페이지에서 인용한 글이다.
2. Express의 Middleware에 대해서 알아보기
- 클라이언트에게 요청이 오고 그 요청을 보내기 위해 응답하려는 중간(미들)에 목적에 맞게 처리를 하는, 말하자면 거쳐가는 함수들이라고 보면된다.
- 미들웨어 함수는 req(요청)객체, res(응답)객체, 그리고 어플리케이션 요청-응답 사이클 도중 그 다음의 미들웨어 함수에 대한 엑세스 권한을 갖는 함수이다.
- 다음 미들웨어 함수에 대한 엑세스는 next함수를 이용해서 다음 미들웨어로 현재 요청을 넘길 수 있다.
- next를 통해 미들웨어는 순차적으로 처리된다.
3. 템플릿 엔진에 대해서 알아보고 실제 페이지를 만들어보기
템플릿 엔진
웹 프로그래밍을 할 때 주로 사용하는 마크업 언어인 HTML은 정적인 언어이다.
따라서 Javascript로 표현하면 반복문으로 간단하게 처리할 수 있는 동적 연산을 HTML으로만 표현하게 되면 일일이 직접 적어주어야 하는데, 이런 과정이 불편해서 나온것이 '템플릿엔진' 이다.템플릿 엔진은 Javascript를 사용해서 HTML을 렌더링할 수 있게 도와주는 도구이다.
app.get('/', (req, res) => { res.send('<!DOCTYPE html>\ <html lang="en">\ <head>\ <meta charset="UTF-8">\ <meta http-equiv="X-UA-Compatible" content="IE=edge">\ <meta name="viewport" content="width=device-width, initial-scale=1.0">\ <title>Document</title>\ </head>\ <body>\ Hi. I am with html<br>\ <a href="/hi">Say Hi!</a>\ </body>\ </html>') }) # 여기서 중요한점은 send() 안에 전체 내용이 ''작은 따옴표로 묶여있다는점 # 마지막줄을 제외한 매 줄마다 마지막에 \ 문자가 들어가 있는점 # 내부에서 쓰이는 문자들은 다 ""쌍 따옴표를 쓰고 있다는점입니다.
위와 같이 코딩을 하게되면, 앞으로 페이지가 추가되면 HTML코드가 길어지므로 템플릿 엔진을 사용을 해야한다.
템플릿 엔진의 장점
- 많은 코드를 줄일 수 있다. → 대부분의 Template Engine은 기존의 HTML에 비해서 간단한 문법을 사용한다.
- 재사용성이 높다. → 웹페이지 혹은 웹앱을 만들 때 똑같은 디자인의 페이지에 보이는 데이터만 바뀌는 경우가 굉장히 많다.
- 유지보수에 용이하다. → 하나의 Template을 만들어 여러 페이지를 렌더링하는 작업에는 또 다른 이점이 있다.
<!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> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.4.0/socket.io.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(category) { $("#goodsList").empty() console.log(category) let goods = [ { "_id": "600fa6e49539b288e3c5a2cf", "goodsId": 4, "name": "상품 4", "thumbnailUrl": "https://cdn.pixabay.com/photo/2016/09/07/02/11/frogs-1650657_1280.jpg", "category": "drink", "price": 0.1 }, { "_id": "600fa6e49539b288e3c5a2ce", "goodsId": 3, "name": "상품 3", "thumbnailUrl": "https://cdn.pixabay.com/photo/2016/09/07/02/12/frogs-1650658_1280.jpg", "category": "drink", "price": 2.2 }, { "_id": "600fa6e49539b288e3c5a2cd", "goodsId": 2, "name": "상품 2", "thumbnailUrl": "https://cdn.pixabay.com/photo/2014/08/26/19/19/wine-428316_1280.jpg", "category": "drink", "price": 0.11 }, { "_id": "600fa6e49539b288e3c5a2cc", "goodsId": 1, "name": "상품 1", "thumbnailUrl": "https://cdn.pixabay.com/photo/2016/09/07/19/54/wines-1652455_1280.jpg", "category": "drink", "price": 6.2 } ] for (let i = 0; i < goods.length; i++) { make_card(goods[i]) } } function make_card(item) { let htmlTemp = `<div> <div class="card mb-2" onclick="location.href='/detail?goodsId=${item["goodsId"]}'"> <div class="row no-gutters"> <div class="col-sm-5" style="background: #868e96;"> <img src="${item["thumbnailUrl"]}" class="card-img-top h-100" alt="..."> </div> <div class="col-sm-7 d-flex"> <div class="card-body flex-fill"> <div class="card-title mb-auto"> <h5 style="display: inline">${item["name"]}</h5> <span class="card-price ml-2">$${number2decimals(item["price"])}</span> </div> <span class="badge badge-secondary">${item["category"]}</span> <!-- <p class="card-text"><small class="text-muted">drink</small></p>--> </div> </div> </div> </div> </div>` $("#goodsList").append(htmlTemp) } function makeNoti(data) { let htmlTemp = `<div class="alert alert-sparta alert-dismissible show fade" role="alert" id="customerAlert"> ${data["userName"]}님이 방금 <a href="#" class="alert-link">${data["goodsName"]}</a>을 구매했어요! <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div>` $("body").append(htmlTemp) } function number2decimals(num) { return (Math.round(num * 100) / 100).toFixed(2); } </script> <style> .card { cursor: pointer; } html { overflow: auto; } </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> <div class="form-group row mr-0"> <label for="categorySelect" class="col-4 col-form-label">카테고리</label> <select class="form-control col-8" id="categorySelect"> <option value="" selected>전체</option> <option value="drink">음료</option> <option value="food">음식</option> </select> </div> </div> <div id="goodsList" class="mb-5"> <div> <div class="card mb-2" onclick="location.href='#'"> <div class="row no-gutters"> <div class="col-sm-5" 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-sm-7 d-flex"> <div class="card-body flex-fill"> <div class="card-title mb-auto"> <h5 style="display: inline">상품 1</h5> <span class="card-price ml-2">$6.20</span> </div> <span class="badge badge-secondary">drink</span> <!-- <p class="card-text"><small class="text-muted">drink</small></p>--> </div> </div> </div> </div> </div> </div> </div> </body> </html>
위와 같이 해서 페이지를 만들어 봤다.
4. 2주차 숙제
숙제 내용
2주차 숙제는 상세페이지인 HTML을 템플릿 엔진을 이용해서 router를 추가하는 것이었다.
1. detail (상세 페이지) HTML
<!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" /> <script> const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const goodsId = urlParams.get("goodsId"); $(document).ready(function() { get_detail(); $("#numberSelect").on("change", function() { let orderNum = parseInt($(this).val()); $("#orderNumber").html( `<small class="mr-2 text-muted">총 수량 ${orderNum}개</small>${number2decimals( orderNum * sessionStorage.getItem("goodsPrice") )}` ); sessionStorage.setItem("orderNum", orderNum); }); }); function sign_out() { $.removeCookie("mytoken", { path: "/" }); $.removeCookie("userName", { path: "/" }); window.location.href = "/"; } function get_detail() { let goods = [ { "_id": "600fa6e49539b288e3c5a2cf", "goodsId": 4, "name": "상품 4", "thumbnailUrl": "https://cdn.pixabay.com/photo/2016/09/07/02/11/frogs-1650657_1280.jpg", "category": "drink", "price": 0.1 }, { "_id": "600fa6e49539b288e3c5a2ce", "goodsId": 3, "name": "상품 3", "thumbnailUrl": "https://cdn.pixabay.com/photo/2016/09/07/02/12/frogs-1650658_1280.jpg", "category": "drink", "price": 2.2 }, { "_id": "600fa6e49539b288e3c5a2cd", "goodsId": 2, "name": "상품 2", "thumbnailUrl": "https://cdn.pixabay.com/photo/2014/08/26/19/19/wine-428316_1280.jpg", "category": "drink", "price": 0.11 }, { "_id": "600fa6e49539b288e3c5a2cc", "goodsId": 1, "name": "상품 1", "thumbnailUrl": "https://cdn.pixabay.com/photo/2016/09/07/19/54/wines-1652455_1280.jpg", "category": "drink", "price": 6.2 } ] let goodsDetail = goods.find((v) => v.goodsId == goodsId); if (!goodsDetail) { goodsDetail = goods[0] } $("#goodsUrl").attr("src", goodsDetail["thumbnailUrl"]); $("#goodsName").text(goodsDetail["name"]); $("#goodsPrice").text("$" + number2decimals(goodsDetail["price"])); sessionStorage.setItem("goodsId", goodsId); sessionStorage.setItem("goodsName", goodsDetail["name"]); sessionStorage.setItem("goodsPrice", goodsDetail["price"]); sessionStorage.setItem("orderNum", 1); } function addCart() { $.ajax({ type: "POST", url: `/api/goods/${goodsId}/cart`, data: { quantity: sessionStorage.getItem("orderNum") }, error: function(xhr, status, error) { if (status == 400) { alert("존재하지 않는 상품입니다."); } window.location.href = "/goods"; }, success: function(response) { if (response["result"] == "success") { $("#cartModal").modal("show"); } } }); } function buyNow() { sessionStorage.setItem( "priceSum", sessionStorage.getItem("goodsPrice") ); sessionStorage.setItem( "cart", JSON.stringify([ { goodsName: sessionStorage.getItem("goodsName"), quantity: sessionStorage.getItem("orderNum") } ]) ); window.location.href = "/order"; } function number2decimals(num) { return (Math.round(num * 100) / 100).toFixed(2); } </script> <title>스파르타 쇼핑몰 | 상품 상세</title> <style></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="row no-gutters"> <div class="col-sm-5"> <img src="https://cdn.pixabay.com/photo/2016/09/07/19/54/wines-1652455_1280.jpg" class="card-img-top h-100" alt="..." id="goodsUrl" /> </div> <div class="col-sm-7 card-body px-3"> <div class="flex-fill mt-3"> <div class="d-flex justify-content-between mb-3"> <h5 style="display: inline" id="goodsName">상품 1</h5> <span class="card-price" id="goodsPrice">$6.20</span> </div> <div class="form-group row mr-0"> <label for="numberSelect" class="col-4 col-form-label" >수량</label > <select class="custom-select col-8" id="numberSelect"> <option selected value="1">1개</option> <option value="2">2개</option> <option value="3">3개</option> <option value="4">4개</option> <option value="5">5개</option> </select> </div> <hr /> <div class="row mb-3"> <div class="col-5">총 상품금액</div> <div class="col-7 text-right" id="orderNumber"> <small class="mr-2 text-muted">총 수량 1개</small>$6.20 </div> </div> <div class="row d-flex justify-content-around"> <div class="col-6 pr-2"> <button type="button" class="btn btn-outline-sparta btn-block" onclick="addCart()" > 장바구니 </button> </div> <div class="col-6 pl-2"> <button type="button" class="btn btn-sparta btn-block" onclick="buyNow()" > 바로 구매 </button> </div> </div> </div> </div> </div> </div> <div class="modal text-left" id="cartModal" tabindex="-1" role="dialog" aria-labelledby="cartModalLabel" aria-hidden="true" > <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="cartModalLabel">알림</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='window.location.href="/cart"' > 장바구니 </button> </div> </div> </div> </div> </body> </html>
2. node.js
const express = require('express') const app = express() const port = 3000 const goodsRouter = require('./routes/goods') const userRouter = require('./routes/user') app.use(express.urlencoded({extended: false})) app.use(express.json()) app.use(express.static('public')); app.use('/goods', goodsRouter) app.use('/user', userRouter) app.use((req, res, next) => { console.log(req); next(); }); app.set('views', __dirname + '/views'); app.set('view engine', 'ejs'); app.get('/test', (req, res) => { let name = req.query.name; res.render('test', {name}); }) app.get('/detail', (req, res) => { res.render('detail') }) app.get('/home', (req, res) => { res.render('index') }) app.listen(port, () => { console.log(`listening at http://localhost:${port}`) })
5. 2주차 소감
스파르타코딩클럽 Node.js 기초반 2주차를 들었다. 1주차는 기본 문법에 대한 코딩을 직접 해보는 시간이 많고 아는 내용들이라서 재밌었는데, 이번 주차 강의는 이론적인게 많았고, 무엇보다도 처음 듣는 용어라서 이해하기가 어려웠다. 처음 듣는거라 이해가 안될 수 있지만
틈틈히 반복학습을 해서 심화반 들어가기전에 기초를 잘 닦아놔야겠다.
'스파르타코딩클럽 Node.js 기초반' 카테고리의 다른 글
[항해4기] 스파르타코딩클럽 Node.js 기초반 5주차 회고록 (0) 2021.11.17 [항해4기] 스파르타코딩클럽 Node.js 기초반 3주차 회고록 (0) 2021.11.16 [항해4기] 스파르타코딩클럽 Node.js 기초반 1주차 회고록 (0) 2021.11.15