짱짱해커가 되고 싶은 나

사용자와 댓글 관리 서버 - 1 본문

Web/Node.js

사용자와 댓글 관리 서버 - 1

동로시 2020. 12. 4. 20:45

DB는 앞에 sequelize를 설명할 때 사용했던 사용자(id, name, age, married, comment, created_at)와 댓글(id, commenter(외래키-user의 id), comment, created_at)을 사용할 것이다.

그 때 얘기한 것처럼 유저와 댓글 DB의 관계는 1:N 관계다.

 

폴더 구조는 express-generator에서 잡아주는 기본 폴더 구조 + sequelize를 통해 추가로 생긴 폴더(models, config, migrations, seeders)이다.

 

view 폴더에서 어떤 식으로 DB의 내용을 보여줄지를 저장하고

public 폴더에서는 각 요청이 들어왔을 때 필요한 데이터들을 저장한다.

필요한 기능에는 댓글 로딩, 사용자 로딩, 댓글 등록, 사용자 등록 + 사용자 이름을 눌렀을 때 댓글 로딩이 있다.

 

프레임워크 : Express

템플릿 : Pug

DB : MySQL + 시퀄라이즈 이용

 

Express-generator는 express <프로젝트 이름>을 입력하면 자동으로 프레임워크에 필요한 package.json과 기본 폴더 구조까지 잡아주는 패키지다. (MVC 패턴과 비슷)

- bin/www // 서버 실행 스크립트

- node_modules

- public //외부에서 접근 가능한 파일들

- routes //주소별 라우터(서버의 로직)

- views //템플릿 파일

- app.js //핵심 서버 역할

- package-lock.json

- package.json

 

더보기

* MVC 패턴 : 모델-뷰-컨트롤러 형태의 디자인 패턴

디자인 패턴은 프로그램을 개발할 때 발생했던 문제점들을 정리해서 상황에 따라 간편하게 적용해서 쓸 수 있는 것을 정리하여 특정한 규약을 통해 쉽게 쓸 수 있는 형태로 만든 것이다. ex)스트래티지 패턴, 옵저버 패턴 등

 

사용자가 컨트롤러를 조작하면 컨트롤러는 모델을 통해서 데이터를 가져오고 그 정보를 바탕으로 시각적인 표현을 담당하는 view를 제어해서 사용자에게 전달한다. (모델->컨트롤러->뷰->유저)

 

모델 : 애플리케이션 정보, 데이터, DB, 데이터의 가공 등 사용자가 편집하길 원하는 모든 데이터다. 그리고 뷰나 컨트롤러에 대한 정보는 존재하지 않는다. (데이터 변경이 일어났을 때 모델에서 직접 수정할 수 있도록 뷰를 참조하는 내부 속성값을 가지면 안된다.) 모델의 텍스트 정보가 변경 됐을 경우에는 이벤트를 발생시켜 송신하고 수신하는 처리방법을 구현해야한다. 모델은 재사용이 가능해야하며 다른 인터페이스에서도 변하면 안된다.

 

뷰는 input, checkbox 등과 같은 사용자 인터페이스로 데이타를 기반으로 사용자들이 볼 수 있는 화면이다.

뷰에 모델이 갖고 있는 정보를 저장하면 안되고 뷰도 뷰만의 구성요소만을 사용해야한다. 그리고 모델과 마찬가지로 모델에 송수신하는 방법을 구현해야한다.

 

컨트롤러는 데이터와 사용자인터페이스 간의 다리역할이다. 컨트롤러는 모델과 뷰에 대해서 알고 있어야하며 이들의 변경을 모니터링 하는 메인 로직을 담당한다.

 

MVC 패턴은 서로 분리되어 자신의 역할에 집중할 수 있게 개발하기 때문에 유지 보수성, 애플리케이션의 확장성, 유연성증가, 중복코딩 문제점이 사라질 수 있다.

ex) react는 뷰만 신경쓰는 프레임워크

 

더보기

Pug(기존의 jade)는 node express의 템플릿 엔진의 일종이다. (express 이름 --view=pug)

이 템플릿 엔진은 HTML을 간단하게 표현하고 마크업 문법보다 코드량이 적어 생산성이 좋다고 한다.

그리고 js 연산 결과를 쉽게 보여주며 정적/동적 부분을 분리할 수 있다.

 

* 마크업(MarkUp) : 마크/태그로 둘러싸인 언어 ex) HTML, XML

* 마크다운(MarkDown) : 마크업 언어의 일종으로 텍스트 <-> HTML 도구이며 읽고 쓰기 쉽다. ex)태그 기능

 

sequelize.pug

doctype html
html
    head
        meta(charset='utf-8')
        title 시퀄라이즈 서버
        style.
            table{
                border: 1px solid black;
                border-collapse: collapse;
            }

            table th, table td{
                border: 1px solid black;
            }
    body
        div
            form#user-form
                fieldset
                    legend 사용자 등록
                    div
                        input#username(type="text" placeholder="이름")
                    div
                        input#age(type="number" placeholder="나이")
                    div
                        input#married(type="checkbox")
                        label(for="married") 결혼 여부
                    button(type="submit") 등록
        br
        table#user-list
            thead
                tr
                    th 아이디
                    th 이름
                    th 나이
                    th 결혼여부
            tbody
                for user in users
                    tr
                        td= user.id
                        td= user.name
                        td= user.age
                        td= user.married ? '기혼' : '미혼'
        br
        div
            form#comment-form
                fieldset
                    legend 댓글 등록
                    div
                        input#userid(type="text" placeholder="사용자 아이디")
                    div
                        input#comment(type="text" placeholder="댓글")
                    button(type="submit") 등록
        br
        table#comment-list
            thead
                tr
                    th 아이디
                    th 작성자
                    th 댓글
                    th 수정
                    th 삭제
                tbody
            script(src='/sequelize.js')

script 태그는 버튼을 눌렀을 때 서버의 라우터로 AJAX 요청을 보내는 코드가 들어있다.

 

더보기

* AJAX : JavaScript의 라이브러리 중 하나이며 비동기식 자바스크립트와 xml의 약자다.

브라우저가 갖고 있는 XMLHttpRequest 객체를 이용해서 전체 페이지를 새로 고치지 않고도 페이지의 일부만을 위한 데이터를 로드할 수 있다. (JS를 사용한 비동기 통신 & 클라이언트와 서버 간에 XML 데이터를 주고받는 기술)

비동기 방식의 장점은 웹페이지를 리로드하지 않고 데이터를 불러올 수 있어서 리소스의 낭비를 막을 수 있다.

따라서 AJAX를 사용하면 JSON이나 xml형태로 필요한 데이터만 받아서 갱신할 수 있기 때문에 자원과 시간을 아낄 수 있다. 그리고 Jquery와 함께 사용하면 동일한 코딩방법으로 더 적은 량으로 동작 할 수 있다. ex) HTTP GET, POST

단점으로는 히스토리 관리가 안되며 XMLHttpReqeust를 통해 통신을 하는 경우 사용자에게 아무런 진행 정보가 주어지지 않기 때문에 요청이 완료되지 않았는데 사용자가 페이지를 떠나면 오작동할 우려가 있다.

 

JSON(Javascript Object Notation) : 키-값 쌍으로 이루어진 데이터 오브젝트를 전달하기 위해 인간이 읽을 수 있는 텍스트를 사용하는 개방형 표준 포맷이다. (일종의 데이터 형식) 

AJAX를 위해 넓게는 XML을 대체하는 주요 데이터 포맷이다.

JSON을 쓰는 이유는 서로 다른 언어들간의 데이터 교환을 편하게 할 수 있기 때문이다.

JSON은 언어가 아니라 데이터 표기방법의 일종인데 XML보다 기능이 적고 가볍기 때문에 쉽게 사용할 수 있다.

 

수, 문자열, boolean, array, object, null의 형식을 지원한다.

object는 {name: value}, array는 [값, 값], value는 ""로 사용한다.

 

JSON 객체는 global 객체다. 

 

JSON.stringify 는 값이나 객체를 JSON 문자열로 반환한다.

JSON.stringify({comment:newComment});

JSON.parse 는 JSON문자열의 구문을 분석하고 그 결과에서 JavaScript 값이나 객체를 생성한다.

 

 

Document 객체

document는 웹페이지 그 자체를 의미한다. 따라서 웹페이지에 존재하는 html 요소에 접근하기 위해서 필요하다.

document 객체는 윈도우 객체의 속성이기 때문에 window.docuemt로 접근해야하지만 window는 생략 가능한 전역 객체이기 때문에 그냥 document로 접근할 수 있다.

 

Document.querySelectorAll()은 지정된 셀렉터 그룹에 일치하는 다큐먼트의 엘리먼트 리소스를 나타내는 정적 nodelist를 반환한다.

css 선택자(selector)로 선택할 수 있는데 아이디는 #, 클래스는 . 이다.

 

1. 사용자 이름을 눌렀을 때 댓글 로딩

elementList = parentNode.querySelectorAll(selectors);
document.querySelectorAll('#user-list tr').forEach(function(el){
    el.addEventListener('click', function(){
        var id = el.querySelector('td').textContent;
        getComment(id);
    });
});

 

위의 코드는 아이디가 user-list tr인 css selector를 nodelist로 반환하고 이 nodelist 객체의 메소드인 foreach를 사용한 것이다.

 

NodeList의 forEach는 리스트 내의 각 쌍에 대해 지정된 콜백을 삽인 순서로 호출한다.

getComment는 JSON토큰 값을 분석하여 string으로 바꿔준다. 

 

즉, 각각의 아이디가 user-listr tr인 것들을 대상으로 click이라는 eventlistner를 생성하고, 이벤트가 발생하면 해당하는 셀렉터의 td부분의 노드와 자식 노드의 텍스트 콘텐츠를 id로 갖고 오는 것이다. 

(사용자 이름을 누렀을 때 댓글을 로딩하는 부분)

 

참고로 user-list의 테이블은 다음과 같이 구성되어 있다.

br
        table#user-list
            thead
                tr
                    th 아이디
                    th 이름
                    th 나이
                    th 결혼여부
            tbody
                for user in users
                    tr
                        td= user.id
                        td= user.name
                        td= user.age
                        td= user.married ? '기혼' : '미혼'
br

 

XMLHttpRequest 객체는 서버와 상호작용하기 위해 사용된다. 전체 페이지의 새로고침 없이도 URL로부터 데이터를 받아올 수 있다. 즉, 웹페이지가 사용자가 하고 있는 것을 방해하지 않으면서도 페이지의 일부를 업데이트 할 수 있다. 이 객체는 주로 AJAX 프로그래밍에 사용된다.

 

이 객체는 XML만 받아오는 것이 아니라 모든 종류의 데이터를 받아올 수 있고 HTTP 외의 프로토콜도 지원한다.

XMLHttpRequest() 생성자는 XMLHttpRequest를 초기화한다.

xhr.onload는 브라우저가 서버로부터 응답을 받을 때 발생하는 이벤트로, 이벤트가 발생하면 함수가 호출된다.

xhr.responseText는 서버에 요청하여 응답으로 받은 데이터를 문자열로 반환한다.

xhr.open(보내는 방식, URL, 비동기 여부) 로 request를 보내고자 하는 사이트의 url을 세팅하는 부분이고

xhr.send는 실제로 보내는 것이다.

 

2. 사용자 로딩

다음 코드는 XMLHttpRequest객체를 이용해 사용자를 로딩하는 부분이다.

function getUser(){
    var xhr = new XMLHttpRequest();
    xhr.onload = function(){
        if(xhr.status === 200){
            var users = JSON.parse(xhr.responseText);
            console.log(users);
            var tbody = document.querySelector('#user-list tbody');
            tbody.innerHTML = '';
            users.map(function(user){
                var row = document.createElement('tr');
                row.addEventListener('click', function(){
                    getComment(user.id);
                });
                var td = document.createElement('td');
                td.textContent = user.id;
                row.appendChild(td);
                td = document.createElement('td');
                td.textContent = user.name;
                row.appendChild(td);
                td = document.createElement('td');
                td.textContent = user.age;
                row.appendChild(td);
                td = document.createElement('td');
                td.textContent = user.married ? '기혼' : '미혼';
                row.appendChild(td);
                tbody.appendChild(row);
            });
        }else{
            console.error(xhr.responseText);
        }
    };
    xhr.open('GET', '/users');
    xhr.send();
}

XMLHttpRequest객체를 생성한다음에 응답이 오고 해당 서버의 응답이 정상이라면 코드를 실행한다.

여기서 console에 찍히는 users 는 아마도 SELECT `id`, `name`, `age`, `married`, `comment`, `created_at` FROM `users` AS `users`; 일 것이다.

tbody에는 id가 user-list tbody인 쿼리들이 저장된다.

그리고 row라는 tr element의 태그를 생성한다. 이 row를 click하면 해당 user의 id를 텍스트 형태로 바꾼다.

그리고 td element의 td 태그를 생성해서 해당 태그에 user.id를 저장한다.

그다음 appendChild를 통해 row에 id를 저장한 td를 연결시킨다.

다음은 이 과정을 반복해서 user.name, user.age, user.married를 row에 이어 붙힌다.

그러면 아마도 users에는 [user.id, user.name, user.age, user.married] 형식으로 만들어질 것이다.

이렇게 만들어진 row를 최종적으로 tbody에 연결시킨다.

 

onload에서 받는 요청은 /users에 보내는 GET 요청으로 이 요청에 대한 응답에 관한 처리다.

 

3. 댓글 로딩

function getComment(id){
    var xhr = new XMLHttpRequest();
    xhr.onload = function(){
        if(xhr.status === 200){
            var comments = JSON.parse(xhr.responseText);
            var tbody = document.querySelector('#comment-list tbody');
            tbody.innerHTML = '';
            comments.map(function (comment){
                var row = document.createElement('tr');
                var td = document.createElement('td');
                td.textContent = comment.id;
                row.appendChild(td);
                td = document.createElement('td');
                td.textContent = comment.user.name;
                row.appendChild(td);
                td = document.createElement('td');
                td.textContent = comment.comment;
                row.appendChild(td);
                var edit = document.createElement('button');
                edit.textContent = '수정';
                edit.addEventListener('click', function(){
                    var newComment = prompt('바꿀 내용을 입력하세요');
                    if(!newComment){
                        return alert('내용을 반드시 입력해야 합니다');
                    }
                    var xhr = new XMLHttpRequest();
                    xhr.onload = function(){
                        if(xhr.status === 200){
                            console.log(xhr.responseText);
                            getComment(id);
                        } else{
                            console.error(xhr.responseText);
                        }
                    };
                    xhr.open('PATCH', '/comments/' + comment.id);
                    xhr.setRequestHeader('Content-Type', 'application/json');
                    xhr.send(JSON.stringify({comment:newComment})); //JSON.stringify : js값이나 객체를 JSON문자열로 변환
                });
                var remove = document.createElement('button');
                remove.textContent = '삭제';
                remove.addEventListener('click', function(){
                    var xhr=new XMLHttpRequest();
                    xhr.onload = function(){
                        if(xhr.status===200){
                            console.log(xhr.responseText);
                            getComment(id);
                        } else{
                            console.error(xhr.responseText);
                        }
                    };
                    xhr.open('DELETE', '/comments/' + comment.id);
                    xhr.send();
                });
                td = document.createElement('td');
                td.appendChild(edit);
                row.appendChild(td);
                td = document.createElement('td');
                td.appendChild(remove);
                row.appendChild(td);
                tbody.appendChild(row);
            });
        }else{
            console.error(xhr.reposneText);
        }
    };
    xhr.open('GET', '/comments/' + id);
    xhr.send();
}

/comments/ + id 로 GET요청을 했을 때의 응답에 관한 부분이다.

 

응답이 정상적으로 왔을 경우 id, name, comment와 edit와 remove 버튼을 tbody에 연결시킨다.

edit와 remove를 좀 더 자세히 보면 click 이벤트가 발생했을 때 수행할 함수가 정의되어 있다.

이 함수 내에서 우선 새롭게 XMLHttpRequest를 생성하고 xhr.onload, open, send를 사용한다.

edit는 /comments/ + comment.id로 PATCH를 요청한다. 이 때 HTTP 요청 헤더에는 'Content-Type'에 'application/json'으로 세팅해서 수정칸의 내용을 JSON형태로 바꿔서 보낸다.

 

4. 사용자 등록

document.getElementById('user-form').addEventListener('submit', function (e){
    e.preventDefault();
    var name = e.target.username.value;
    var age = e.target.age.value;
    var married = e.target.married.checked;
    if(!name){
        return alert('이름을 입력하세요');
    }
    if(!age){
        return alert('나이를 입력하세요');
    }
    var xhr = new XMLHttpRequest();
    xhr.onload = function(){
        if(xhr.satus === 201){
            console.log(xhr.responseText);
            getUser();
        } else{
            console.error(xhr.responseText);
        }
    };
    xhr.open('POST', '/users');
    xhr.setRequestHeader('Content-type', application/json);
    xhr.send(JSON.stringify({name: name, age: age, married: married}));
    e.target.username.value = '';
    e.target.age.value = '';
    e.target.married.checked = false;
});

id가 user-form인 부분에 submit 리스너를 등록한다.

submit을 누를 경우 가장 먼저 e.preventDefault()를 실행하는데 이 함수는 submit태그의 고유 동작인 input 전송 등을 중단 시키는 함수다.

그리고 입력한 nae, age, married의 체크 여부를 각각 저장한다.

그 다음 XMLHttpRequest()를 생성해서 submit 의 동작들을 만든다.

/users 에 POST요청을 할 때 Header의 Content-Type을 정의해서 name, age, married에 대한 정보를 각각 JSON으로 만들어 전송한다.

 

5. 댓글 등록

document.getElementById('comment-form').addEventListener('submit', function(e){
    e.preventDefault();
    var id = e.target.userid.value;
    var comment = e.target.comment.value;
    if(!id){
        return alert('아이디를 입력하세요');
    }
    if(!comment){
        return alert('댓글을 입력하세요');
    }
    var xhr = new XMLHttpRequest();
    xhr.onload = function(){
        if(xhr.stauts === 201){
            console.log(xhr.responseText);
            getComment(id);
        } else{
            console.error(xhr.responseText);
        }
    };
    xhr.open('POST', '/comments');
    xhr.setRequestHeader('Content-type', 'application/json');
    xhr.send(JSON.stringify({id: id, comment: comment}));
    e.target.userid.value = '';
    e.target.comment.value ='';
});

id가 comment-form인 부분에 submit이벤트를 등록해서 해당 이벤트가 발생하면 id와 comment를 받아오고 /comment에 POST요청이 들어오고 응답코드인 201이 들어온다면 getComment(id)를 한다. 이 때 id와 comment에 대한 정보를 JSON으로 만들어서보낸다.

'Web > Node.js' 카테고리의 다른 글

사용자와 댓글 관리 서버 - 2  (0) 2020.12.05
sequelize  (0) 2020.11.22
[http 모듈] 쿠키  (0) 2020.10.25
Node.js 훑어보기  (1) 2020.08.07
Comments