본문 바로가기
Node.js

댓글 기능이 있는 게시판 만들기 (Node.js, MongoDB, Express 사용)

by SuldenLion 2023. 7. 31.
반응형

CRUD 기능과 댓글 추가/삭제 기능을 가진 게시판을 Node.js 환경에서 만들어 보겠다.

 

웹 애플리케이션 서버로써 Express를 사용하고, 데이터베이스는 MongoDB를 사용하도록 할 것이다.

 

해당 내용은 박승규 저자의 Node.js 백엔드 개발자 되기 책의 내용을 참고한다.

 

 

만들어갈 게시판 프로젝트는 다음과 같은 구조로 동작한다.

 

 

- HTML 템플릿 엔진은 핸들바로 작성

- Controller는 따로 디렉터리 구분없이 app.js 안에 라우터 함수들로 분리

- Service는 일단 하나만

- Model에서 DB와 통신하는 구조 (명시적으로 모델을 만들지는 않고, MongoDB 라이브러리의 Collection 객체가 모델역할을 할것임)

 

 

▷ 프로젝트 초기 설정

- 익스프레스, 핸들바, 몽구스 패키지 설치

- 적당히 디렉터리 구조를 잡아주고, npm init -y를 통한 package.json 생성 (package.json은 Node.js 프로젝트를 관리하고 빌드하는데 필요한 기본 정보들과 의존성 관리를 위한 파일, 프로젝트 구성과 관리를 쉽게해줌)

- 해당 디렉터리에 express, mongoDB 설치

 

 

 

package.json의 dependency에 Express와 mongoose 추가 확인

 

 

▷ 게시판 프로젝트 디렉터리 구조 

- Server 개발에서 가장 보편적으로 사용되는 3계층 구조 아키텍처(3 Tier Architecture)를 적용하겠다.

↳ Web Framework에서 주로 사용되는 MVC 패턴 적용에도 좋음

- 3 계층 구조는 Controller, Service, Data Access 계층을 가지며, Controller 계층에서는 권한 체크, 유효성 검증 등을 한 후 Service 계층으로 넘긴다. 

- Service 계층에서는 비즈니스 로직을 처리한다. (서비스로 비즈니스 로직을 분리하면 각각 다른 컨트롤러에서 같은 서비스를 재사용할 수 있음)

- Service에서 DB에 데이터 저장시 Data Access 계층과 데이터를 주고받는다. (정의해둔 모델을 넘김)

 

> 3 계층 아키텍처

 

> Express에서의 3 계층 아키텍처

 

 

↳ Express에서 Controller 역할은 Router가 함

- MongoDB 모듈이 Data Access 계층 역할을 해준다. (실제 서비스에서는 데이터 액세스 계층에 다양한 DB 사용)

 

 

▷ 게시판 프로젝트 데이터 흐름

 

 

▷ 핸들바 템플릿 엔진 설치 & 설정

- 핸들바 템플릿 엔진은 Controller에서 넘기는 데이터를 웹 페이지에서 표현하는데 사용될 것이다.

API만 만든다면 템플릿 엔진 필요없지만, 뷰로 웹페이지를 보여주므로 템플릿 엔진을 설정해야 컨트롤러에서 넘기는 데이터를 웹페이지에 제대로 표현 가능.

 

핸들바 설치

 

- app.js 파일을 생성하고 설정을 진행한다.

- app.engine()에 템플릿 엔진으로 핸들바를 등록한다.

- app.set()에는 웹 페이지 로드시 사용할 템플릿 엔진 설정과 뷰 디렉터리를 views로 설정하는 코드를 작성한다.

- app.get()으로 라우터를 설정한다.

- home이 템플릿 파일의 이름, views가 기본 경로이고 handlebars가 확장자이므로 "views/home.handlebars" 파일에 데이터를 랜더링한다. (랜더링 할때 title과 message 값이 객체로 들어감)

 

- board 밑에 views/layouts 경로를 추가하고 main.handlebars 템플릿 파일을 만들겠다.

main.handlebars

handlebars 파일의 {{{ body }}} 부분에 다른 핸들바 템플릿의 코드가 그대로 들어간다.

 

+) handlebars.engine에 layoutsDir 항목 추가시 기본 레이아웃 디렉터리 변경가능

+) 혹은 기본 레이아웃 사용을 원치 않으면 라우터 결과객체에 layout: false 추가

 

- views 경로 아래에 home.handlebars 추가

↳ 핸들바는 랜더링시 {{ 변수명 }}으로 되어있는 부분에 변수값 지정

 

 

 

- node로 app.js 실행 후 동작 확인

 

 

▷ UI 화면 만들기

 

- 화면에 보여줄 검색어 영역, 테이블, 페이징 영역을 home.handlebars에 만들어준다.

- app.js 파일의 render부분(title)을 "SuldenLion's Board"라고 수정하고 서버를 가동시켜 테스트해본다.

 

> 글 작성 페이지 템플릿

- form 태그를 사용하고, 다른 곳에서 폼을 찾을 수 있게 name을 준다.

- action에 Server의 주소값을 넣고 서버에서는 POST이며 url이 write인 핸들러 함수를 만들어야 한다.

app.js에 코드 추가

 

/write 웹 실행 화면

 

- 마찬가지로 Detail 페이지도 만들어주고 app.js에 핸들러 함수를 추가한다.

 

 

 

 

detail 출력 화면

 

 

▷ MongoDB 연결 유틸리티 만들기

- MongoDB 커넥션을 반환하는 유틸리티 함수를 만들어서 사용하겠다.

 

connection 파일을 만들고 uri에 본인의 id, password, cluster 정보(아틀라스 서버 주소)를 기입한다.

 

- uri의 마지막에 board(test)가 있는데 기본값으로 선택하는 DB를 의미한다. DB를 명시적으로 생성하지 않으면 첫 데이터가 추가될 때 지정한 DB도 자동 생성된다.

- module.exports 의 function에는 함수를 호출하는 사람이 MongoDB의 uri 값을 몰라도 사용할 수 있게 함수를 한번 감쌌다. (원래는 MongoClient.connect(uri, callback)을 실행해야하지만 mongodb-connection(callback)으로 실행할 수 있다는 의미)

 

- app.js에 커넥션에 관련된 코드를 추가한다.

- 만들어 둔 MongoDB 연결용 함수를 import한다.

- 불러온 함수를 실행하면 결과 값으로 mongoClient 객체를 받을수 있다.

- mongoClient에서 db()를 사용해 DB를 선택하고, collection("post")를 사용해 컬렉션을 선택한다.

 

- 이제 핸들바의 기본 설정은 마쳤다. 

- 핸들바에서 each와 if 같은 매우 기본적인 헬퍼 함수는 제공해주지만, 그 외에 것은 모두 만들어서 커스텀 헬퍼 함수를 구현해 사용해야 한다.

- 리스트 길이 구하는 함수, 두 값이 같은지 비교하는 함수, ISO 데이터 포맷에서 날짜만 뽑아내는 커스텀 헬퍼 함수 3개가 필요하다.

 

핸들바 커스텀 헬퍼는 위와 같이 만들고, 필요한 3개의 값을 반환하는 코드를 넣는다.

 

헬퍼 함수 사용시 아래의 방식과 같이 쓴다.

{{ lengthOfList comments }}개의 댓글이 있음.
작성일자 : {{ dateString createDt }}
{{ #if (eq . @root.paginator.page)}}eq 테스트{{/if}}

'.'과 '@root'는 각각 현재 객체와 최상위 객체를 의미함.

 

- app.js에 핸들바 커스텀 함수 설정을 해준다. 

기본의 app.engine() 코드를 수정

- handlebars.create() 함수를 통해 핸들바 생성과 엔진 반환을 한다.

 

 

▷ nodemon 설정하기

- 서버 코드 작성시 매번 서버를 껏다 켰다 하는 것은 번거로우므로 코드 저장시에 서버를 재시작 시켜주는 도구인 nodemon 패키지를 설치한다.

- 터미널에 위의 명령어를 입력해주고 package.json 파일 "scripts" 부분에 코드를 추가한다.

 

 

▶ 글쓰기 API 만들기

- 우선, app.js에 HTTP 요청의 body를 해석하기 위한 설정을 추가해준다.

 

- 그리고 아래와 같이 서비스 파일 로딩을 위한 변수를 둔다.

 

- 또한, post 처리를 해줄 app.post() 함수를 둔다.

↳ postService.writePost()로 글쓰기 후 결과를 반환받고, result.inserted라는 식별자로 사용할 값을 사용해 상세페이지로 이동한다.

 

 

- 다음으로 services 폴더를 만들어 post-service.js 파일을 만들어준다.

- writePost 함수는 post를 board 컬렉션에 저장할 것이다.

- 조회수와 생성일자를 넣어준다.

- return문은 MongoDB에 post를 저장 후 결과를 반환한다.

- module.exports 는 require()로 파일을 import 시 외부로 노출하는 객체이다.

 

> 서버 테스트

- npm start 명령으로 서버를 띄우고 저장을 확인해보겠다.

 

- 각 컴포넌트를 채워넣고 Save한다.

 

 

- URL에 detail/{게시글id}가 나타나면 성공

 

 

- 또한, MongoDB Compass에서 board 테이블의 post 컬렉션을 보면 데이터가 저장된걸 볼 수 있다.

 

 

리스트 API 만들기

- 검색과 페이지네이션을 포함하는 List API를 만들어 보겠다.

- 먼저 템플릿 파일의 검색창 부분을 수정한다. (검색어 넣고 검색 버튼 클릭시 검색어 정보를 담아서 서버에 요청)

 

home.handlebars 검색어 부분 수정

- 버튼 클릭시 id에 따른 value 값을 검색하도록 링크를 설정한다.

 

- app.js 파일의 라우터 함수 app.get("/")을 수정한다.

↳ get으로 API를 호출해 URL 뒤에 변수를 추가하는 경우 req.query 객체로 변수의 값을 받아온다.

↳ 리스트 페이지이므로 현재 페이지 데이터와 검색어 데이터를 req.query의 page, search로 각각 가지고 있다.

'||'는 이전 값이 빈 값(null)인 경우 '||' 뒤의 값을 기본값으로 설정한다.

 

- list 데이터를 가져오는 로직은 모두 postService.list()에 있고, 글 목록과 페이지네이터를 가져온다.

- 그리고 리스트 페이지를 랜더링한다.

 

- 다음은 postService에 list() 함수 추가를 하겠다.

- utils의 paginator를 import 해오는 코드를 추가한다.

- list() 함수는 collection, page, search 매개변수를 받는다.

- 지역변수 perPage는 한 페이지에 노출할 글 개수이다.

- 글 목록 리스트를 가져오려면 MongoDB collection의 find() 함수를 사용해야 한다. find() 함수는 find(query, option)으로 구성되며, 뒤에 sort()로 정렬도 가능하다.

 

cursur → cursor (오타)

- query는 title이 search와 부분일치하는지 확인한다. 

- cursor에서 limit 10개만 가져오고, skip은 설정된 개수만큼 건너뛴다. 그리고 생성일 역순으로 정렬한다.

- totalCount는 검색어에 걸리는 게시물의 총합이다.

- posts는 cursor로 받아온 데이터를 리스트로 변경한다.

- paginatorObj는 totalCount와 page, perPage에 대한 정보로 페이지네이터를 생성한다.

- module.exports에 list를 추가해준다.

 

 

- 다음으로는 페이지네이션 유틸을 작성한다.

- 페이지네이터 부분에 1부터 10페이지까지 나오게 한다. 시작부터 끝 페이지까지 숫자가 들어있는 리스트를 편리하게 만들수 있는 함수가 lodash.range()라는 함수이다. (lodash.range(1,11) 실행시 1~10으로 구성된 리스트가 반환됨)

- 터미널에 lodash 사용을 위한 설치 명령어를 입력한다.

- PAGE_LIST_SIZE에 최대 몇개의 페이지를 보여줄지 정한다.

- 페이지네이터는 하나의 함수로 이루어져 있고, 변수로 총 개수, 현재 페이지, 한 페이지당 표시하는 게시물 개수를 받을 것이다.

- 총 페이지 수와 시작 페이지, 끝 페이지를 구한 후 표시할 페이지 번호 리스트를 만들어주는게 페이지네이터의 전부이다.

 

 

- 다음은 리스트와 페이지네이션을 템플릿에 추가하겠다.

- home.handlebars에 적혀있던 임시 데이터들을 아래와 같이 수정한다.

 

스프링으로 게시판 만들때 jstl 쓰던 것과 비슷하다. (해설 생략)

 

 

▷ 상세페이지 API 만들기

- 다음은 app.js에 상세페이지 라우터 설정과 로직을 추가하겠다.

 

- 상세페이지 API는 단순히 id 정보를 넘겨서 MongoDB에 있는 데이터를 가져오게 한다.

 

- projection은 투영이라는 뜻, DB에서 필요한 필드드만 선택해서 가져오게 한다. (게시글의 패스워드와 게시글에 달린 댓글의 패스워드 항목을 가져오지 않아도 되므로 사용)

- getDetailPost() 함수는 한개의 게시글 정보를 가져온다. 게시글의 정보를 가져오는 역할과 읽을 떄마다 조회수(hits)를 1씩 증가시키는 역할을 한다.

- findOneAndUpdate는 id로 원하는 정보를 가져오고, $inc: { hits: 1 }는 id로 찾은 도큐먼트에 갱신할 update 변수이다. 3번째 parameter로는 projectionOption 같은 프로젝션 또는 sorting 등의 항목을 넣을 수 있다.

 

 

- detail.handlebars 수정

- with 내장 함수를 사용해 게시글 객체를 post. 없이 접근하도록 한다.

- 수정, 삭제를 위한 함수 body를 만들어둔다.

 

 

> 글 수정 API

- 패스워드를 확인하고 글 수정을 할 수 있게 하는 기능을 만들겠다.

- 화면 이동없이 패스워드 확인하기 위해 Ajax를 사용한다. (fetch API를 사용하여 패스워드 확인 API 호출)

detail.handlebars에 modify 내용 추가

 

그리고 app.js에 패스워드 체크 api를 추가해준다.

 

- postService에 id와 password로 게시글 데이터 가져오는 로직을 추가시킨다.

 

- app.js에 페이지 이동 관련 로직 추가

 

- 글쓰기 페이지를 수정 페이지로 변경하기

mode에 따라 글 작성 혹은 글 수정으로 나오도록 한다. 그리고 두가지 경우에 따라 필드를 보여주거나 숨기거나 한다.

 

> 글 삭제 API

- 삭제 관련 로직을 detail.handlers에 추가한다.

↳ 프롬프트로 값을 입력받고, fetch API를 사용해 delete api를 호출한다. delete api의 결과에 따라 다른 메시지를 출력한다.

 

- 서버쪽에 삭제 동작 코드를 넣겠다.

app.js에 해당 코드들을 추가시킨다.

collection의 deleteOne() 함수를 사용해서 게시글 하나를 삭제하고, 삭제 결과가 잘못됐을때의 처리를 한다.

 

 

> 댓글 추가 API

- RDB에서 게시글에 댓글을 추가하려면 일반적으로 댓글용 테이블을 만들고 댓글 테이블에 게시글 ID를 외래키로 사용해 데이터를 추가한다.

- 특정 게시글의 댓글을 찾으려면 반드시 다른 테이블을 조회해야 한다. 

- 저장시의 스키마가 따로 필요 없는 MongoDB에서는 테이블을 새로 만들지 않아도 된다. (게시글의 필드로 댓글을 추가할 것임 → 필드 데이터 크기가 크지 않으므로 큰 규모의 커뮤니티 서비스가 아니라면 문제 없음)

 

- detail.handlebars의 댓글 폼 영역을 수정해준다.

 

- app.js에 댓글 추가 API를 만들어준다.

body에서 데이터를 가져온다. 게시글에 기존 댓글 리스트가 있으면 추가해주고, 없다면 댓글 정보를 추가한다. 함수 끝에는 업데이트와 상세 페이지로 리다이렉트를 해준다.

 

- 마지막으로 댓글 삭제 API이다.

↳ detail.handlebars에 comment 삭제에 대한 로직을 넣는다.

 

그리고 app.js에 /delete-comment에 대한 작업을 한다.

게시글(post)의 comments 안에있는 특정 댓글 데이터를 찾고, 데이터가 없으면 isSuccess : false를 준다. 있으면 댓글 번호가 idx 이외인 것만 comments에 다시 할당 후 저장한다.

 

 

동작 영상

 

반응형

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

MongoDB에 대하여  (0) 2023.07.30
Node.js & 익스프레스 사용 정리 (WAS 구현)  (0) 2023.07.27
자바스크립트 비동기 처리 정리  (0) 2023.07.26
Node.js에 대하여  (2) 2023.07.26

댓글