학습 기록/데브코스 웹 풀스택 4기

웹 서비스의 이해 (3) - 쇼핑몰 페이지 제작 실습 시작과 백엔드 기초

romi__ 2024. 8. 22. 15:14

/date 24.08.22.

 

 

아래 예시와 유사하게 테니스 라켓을 판매하는 쇼핑몰 페이지를 제작해 보려고 합니다. 메인 화면에선 테니스 라켓의 사진을 보고 order 버튼을 통해 물건을 담을 수 있고, order list 화면에서는 어떤 물건을 담았는지 확인할 수 있습니다.

 

 

먼저 메인 페이지를 작성해 보겠습니다. CSS는 외부 스타일 시트로 빼서 따로 만들 생각이라 일단 HTML 코드만 작성해 주었습니다.

 

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title> Main </title>
        
    </head>
    <body>
        <h1>Tennis Market</h1>
        <div>
            Welcome to Tennis Market! <br>
            Enjoy your shopping.
            <br>
            <br>
            <a>order list</a>
        </div>
        
        <div class="card">
            <img class="card_img" src="./img/redRacket.png">
            <p class="card_title"> Red Racket </p>
            <input class="card_button" type="button" value="order" onclick="alert(1);">
        </div>

        <div class="card">
            <img class="card_img" src="./img/blueRacket.png">
            <p class="card_title"> Blue Racket </p>
            <input class="card_button" type="button" value="order" onclick="alert(2);">
        </div>

        <div class="card">
            <img class="card_img" src="./img/blackRacket.png">
            <p class="card_title"> Black Racket </p>
            <input class="card_button" type="button" value="order" onclick="alert(3);">
        </div>

    </body>
</html>

 

 

그럼 아래와 같은 화면으로 완성됩니다. 아직 CSS를 적용하지 않아 스크롤을 내려야 모든 라켓의 사진을 볼 수 있습니다.

 

 

외부 스타일 시트를 작성하여 적용해 보겠습니다. CSS 코드는 아래와 같이 적용하였습니다.

 

h1 {
    text-align: center;
}

#welcome {
    text-align: center;
    margin-bottom: 50px;
}

#card_style {
    display:grid;
    grid-template-columns: 1fr 1fr 1fr;
}

.card {
    text-align:center;
}

.card_title {
    font-size:20px;
}

.card_img {
    width:70%;
}

.card_button {
    font-size: 25px;
    width:150px;
    height: 30px;
}

 

쓰고 나서 보니 위에서 공유한 코드의 클래스나 아이디에서 좀 달라진 게 있네요. 머...쩝... 어쩔 수 없습니다. 암튼 간에 이렇게 CSS를 작성하고 스타일 시트를 연결해 주면 아래와 같이 화면이 구성됩니다.

 

제법 비슷해졌죠?

 

그럼 이제 order list 페이지를 구성해 보겠습니다. 데이터 받아오는 것 없이 틀만 한 번 만들어 보겠습니다.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title> Order List </title>
        <style>
            h1 {
                text-align: center;
            }

            div {
                text-align: center;
                margin-bottom: 50px;
            }

            table {
                margin-left:auto;
                margin-right:auto;
                text-align: center;
            }
        </style>
    </head>
    <body>
        <h1> Order List </h1>
        <div>
            <a href="./main.html"> go back to main page </a>
        </div>

        <table style="border: 1px solid black;"> <!--열 제목--> 
            <th> No. </th>
            <th> Product </th>
            <th> Description</th>
            <th> Price </th>
            <th> Order Date </th>

            <tr> <!--첫 번째 행-->
                <td>1</td>
                <td>product 1</td>
                <td>description 1</td>
                <td>price 1</td>
                <td>date 1</td>
            </tr>

            <tr> <!--두 번째 행-->
                <td>2</td>
                <td>product 2</td>
                <td>description 2</td>
                <td>price 2</td>
                <td>date 2</td>
            </tr>
        </table>
    </body>
</html>

 

이번에는 외부 스타일 시트 연결하지 않고 내부 스타일 시트를 작성해 보았습니다. 결과는 아래와 같습니다.

 

제법 원했던 모양새가 나오고 있습니다. 그렇다면 이제 백엔드에 대해 알아볼 시간입니다.

 

백엔드

백엔드의 구조

백엔드는 어떤 구조를 갖추고 있을까요?

 

웹 서버는 정적 페이지에 대해 대응합니다. 정적 페이지란, 화면의 내용, 데이터 등의 변동이 없는 페이지를 말합니다. 즉 언제나 같은 정보를 뿌려 주는 페이지가 정적 페이지입니다. 정적 페이지는 데이터베이스와의 소통도, 사용자와의 소통도 하지 않습니다. 이러한 페이지를 웹 서버가 처리합니다.

 

정적 페이지가 있다면 동적 페이지도 있습니다. 동적 페이지란 데이터 처리/연산을 통해 화면의 내용 및 데이터가 변화하는 페이지를 의미합니다. 검색을 통해 데이터베이스에서 정보를 긁어 와야 하거나, 로그인을 하는 등 페이지가 변화해야 하는 부분을 담당합니다. 이러한 동적 페이지에 대한 처리는 웹 서버가 직접 처리하지 않고, 웹 어플리케이션 서버에게 전달합니다.

 

웹 어플리케이션 서비스는 앞서 언급하였듯 동적 페이지를 처리합니다. 필요한 데이터 연산을 위해 데이터베이스와 연결되어 있으며, 데이터 조회, 수정, 삭제에 대한 처리를 요청하곤 합니다.

 

 

Node.js 소개 및 설치, 웹 서버 만들어 보기

그렇다면 이제 작성한 페이지의 서버를 구축할 차례입니다. Node.js를 통해 만들어 봅시다.

 

먼저 Node.js란, 자바스크립트를 스크립트 언어 이상으로, 즉 프로그래밍 언어의 역할을 할 수 있도록 지원하는 플랫폼입니다. 우리는 Node.js를 활용한다면 자바스크립트로 백엔드를 구현할 수 있습니다.

 

 

구글에 Node.js를 검색해서 접속할 수 있는 공식 홈페이지에서 다운로드할 수 있습니다. 터미널을 통해 설치해보려 하였으나 nvm 설치도 안 되어 있고 귀찮아서 그냥 prebuilt installer를 이용했습니다.

 

무사히 설치를 완료했습니다. 그렇다면 vscode에서 웹서버를 만들어 보아요.

 

let http = require('http'); //node.js가 갖고 있는 모듈을 부르는 함수

//request, response 변수는 node.js가 알아서 넣어줌
function onRequest(request, response) {
    response.writeHead(200, {'Content-Type' : 'text/html'});
    response.write('Hello Node.js');
    response.end();
}

//서버를 만들어주는 함수 createServer
http.createServer(onRequest).listen(8888);

 

이렇게 코드를 작성하고, 터미널에서 node 파일명 으로 실행하였습니다.

 

브라우저에서 localhost:8888로 접속하니 제가 작성한 코드가 정상적으로 작동하는군요. 성공입니다. 얏호!

 

기본적인 HTTP 프로토콜 템플릿은 아래와 같습니다.

  • Head
    • 통신 상태가 어떤지 알려줍니다: HTTP (status) code를 통해 서버가 정상적으로 작동하는지, 클라이언트가 원하는 바를 찾았는지 알려줍니다. 우리가 자주 보는 200, 404, 500 error가 바로 이 코드입니다.
    • 응답이 어떤 형태인지 적어줍니다.
  • Body
    • 웹 페이지 화면에 뿌려질 데이터를 담는 부분입니다.

 

이러한 정보를 바탕으로 다시 위에서 작성하였던 코드 속 function을 뜯어보면,

function onRequest(request, response) {
    response.writeHead(200, {'Content-Type' : 'text/html'});
    response.write('Hello Node.js');
    response.end();
}

 

  • 웹 서버가 클라이언트에게 응답을 해 줄 때, head의 내용을 적습니다: 200(정상)이고, 클라이언트에게 줄 response 타입은 HTML
  • 화면에 뿌려질 데이터는 'Hello Node.js'
  • 이제 response에 담을 내용은 끝났으니 전송

이렇게 해석해볼 수 있습니다.

 

그렇다면 작성한 서버(저의 경우 server.js)를 모듈화 해보겠습니다. 모듈화란 우리가 사용하고 있는 파일을 다른 자바스크립트 파일에서도 사용할 수 있도록 해주는 과정입니다.

 

let http = require('http'); //node.js가 갖고 있는 모듈을 부르는 함수
let url = require('url');

//자바스크립트 함수는 함수가 작성된 파일 안에서만 사용 가능
//밖으로 빼내겠다고 알려줘야 다른 파일에서도 사용 가능
function start(route, handle) {
    //request, response 변수는 node.js가 알아서 넣어줌
    function onRequest(request, response) {
        let pathname = url.parse(request.url).pathname;
        route(pathname, handle, response);
    }

    //서버를 만들어주는 함수 createServer
    http.createServer(onRequest).listen(8888);
}

//바깥에서 start를 사용할 수 있도록 할게~!
exports.start = start;

 

server.js를 이렇게 작성하였습니다.

 

function route(pathname, handle, response) {
    console.log('pathname : ' + pathname);

    handle[pathname](response);
}

exports.route = route;

 

router.js

 

function main(response) {
    console.log('main');

    response.writeHead(200, {'Content-Type' : 'text/html'});
        response.write('This is Main Page by romi');
        response.end();
}

function login(response) {
    console.log('login');

    response.writeHead(200, {'Content-Type' : 'text/html'});
        response.write('This is Login Page');
        response.end();
}

function favicon(response) {
    console.log('favicon');

    response.writeHead(200, {'Content-Type' : 'text/html'});
        response.write('Stop Making Error');
        response.end();
}

let handle = {}; //key:value쌍으로 이루어진 변수 상자
//말하자면 사전처럼 사용됨
handle['/'] = main;
handle['/login'] = login;
handle['/favicon.ico'] = favicon;

exports.handle = handle;

 

requestHandler.js

 

let server = require('./server');
let router = require('./router');
let requestHandler = require('./requestHandler');
//모듈을 불러와서 내가 실행하고 싶을 때만 실행시키고 싶음
server.start(router.route, requestHandler.handle);

 

index.js입니다.

 

그럼 터미널을 통해 실행시켜 보겠습니다.

 

 

localhost:8888/로 메인 페이지를 접속하니 제가 작성했던 대로 response가 나타납니다. URL을 /login으로 고쳐보겠습니다.

 

제대로 작동하는군요! 야호!

 

 

+ typeError 해결하는 방법

 

강의대로 진행하던 도중, 오류가 발생하였습니다.

 

타입에러 발생했어~ 너가 쓴 handle[pathname] 이거 함수 아닌데?

 

엥 이게 무슨 소리죠? 이것도 함수로 동작한다면서요 강사님~! 역시 백엔드는 내 길이 아닌가봐 흑흐그긓긓긓흑흑,,,, 이라고 생각하다가 문명이 이기를 활용하기 위해 구글링을 해봤습니다. 저와 동일한 문제를 겪은 분들이 계시더군요.

 

저의 경우 favicon도 함수를 하나 만들어줘서 해결했습니다. requestHandler.js 파일에 favicon이 떴을 경우 response를 어떻게 할지 지정해 주었고, 코드 삽입 후 재실행을 해보니 문제없이 실행할 수 있었습니다.

 

function favicon(response) {
    console.log('favicon');

    response.writeHead(200, {'Content-Type' : 'text/html'});
        response.write('Stop Making Error');
        response.end();
}

let handle = {}; //key:value쌍으로 이루어진 변수 상자
//말하자면 사전처럼 사용됨
handle['/'] = main;
handle['/login'] = login;
handle['/favicon.ico'] = favicon;

exports.handle = handle;

 

위의 function을 넣어주니 오류가 사라졌습니다. 처음 서버를 가동할 때도 console log를 찍어보았을 때 pathname : / 만 뜨는 것이 아니라 /favicon.ico도 같이 떠서 엥...? 싶었는데 이런 문제를 일으키는군요. 찾아보니 이 favicon이라는 것은 웹 브라우저가 자동으로 요청하는 아이콘 파일인데, 제가 작성한 코드에는 해당 경로에 대한 처리 로직이나 함수가 없어서 오류가 난 것 같습니다.

 

챗GPT에게도 물어봤더니 살짝 다른 해결책을 주었습니다.

function route(pathname, handle) {
    console.log('pathname : ' + pathname);
    
    // handle[pathname]이 있는지 확인
    if (typeof handle[pathname] === 'function') {
        handle[pathname]();  // 함수가 존재하면 호출
    } else {
        console.log('No request handler found for ' + pathname);  // 존재하지 않는 경로에 대한 오류 메시지 출력
    }
}

exports.route = route;

 

router.js 파일을 이렇게 고치는 해결책을 주더군요. 해결하는 방식은 다르지만 결론적으로 문제에 대한 인식은 같습니다. handle[pathname]이 function으로 작동할 수 있는 상황에서는, 즉 경로에 대한 처리 로직과 함수가 전부 갖춰져 있는 상황에서는 기존에 작성하였던 handle[pathname]을 그대로 호출하고 그렇지 않은 경우 오류 메시지를 출력하도록 하는 것입니다.

 

하나의 문제에 대해 다양한 방법으로 해결할 수 있다는 점이 코딩의 매력 중 하나라고 생각이 듭니다.