본문 바로가기

프로젝트/개인 프로젝트

230509~230511_Toy project_MVC 패턴을 이용한 게시판 페이지 제작

728x90

 


개요

 node.js를 배우면서 Mysql과 연결하여 서버를 만드는 작업을 반복적으로 진행하였다. 교수님께서 입력하시는대로 코드를 받아적고 있으니 동작이 되는 것을 확인할 수는 있지만, 아직 그 개념을 익히기엔 어려웠으므로 이번에는 스스로 페이지를 만들어볼 것을 과제로 내주신 것으로 생각된다.

 과제의 요구사항은 아래와 같다.

  • MVC 패턴으로 새로운 프로젝트를 구성한다.
  • 유저 회원가입, 로그인 제작
  • 게시판 제작
    • 페이지에 css와 이미지를 적용시킨다.
    • 글 목록 페이지
      • 목록 페이지에는 글 제목만 노출된다.
    • 글 작성 페이지
    • 글 상세 페이지
    • 글 좋아요 기능이 들어간다.
    • 글에 덧글을 등록할 수 있다.
      • 익명의 등록자

이 요구사항들을 충족시키며 페이지를 구성하되, 가장 중요한 것은 아래의 내용이다.

 " 전에 배운 내용을 참고해도 괜찮은데, 작성하면서 코드가 무슨 동작을 하게 해주는지 이해하면서 작성한다. 완성시키는 것도 중요하지만 이해를 목표로 작업해야한다."

 

 

 


기획과정

 우선 '페이지를 어떻게 구성할 것인가'를 가장 먼저 고민했다. 회원가입과 로그인을 할 수 있도록 페이지를 구성하긴 하지만, 로그인을 동작했을 때 별도의 데이터를 받아서 작업하고자 하진 않았으므로, 게시판에 중점을 두고 작업을 하고자 하였다. 

 따라서, 게시판 목록을 가장 메인으로 두고, 게시판 목록 페이지의 제목을 누르면 '게시판 상세'페이지로 넘어갈 수 있도록 제작하고, 그 안에 좋아요, 댓글, 수정, 삭제의 모든 기능을 넣어주고자 하였다.  

 

 

 


제작과정

 이전에 들은 수업 내용을 토대로 먼저 제작순서와 내용을 참고하면서 작업을 진행해야 했다. 안보면서 작업을 하면 너무 좋겠지만 아직 수업때 작업한 내용도 전부 이해하지 못했기 때문에 참고하지 않고 작업을 진행하기에 어려웠다.

 해당 프로젝트에서 사용한 모듈은 'express', 'path', 'mysql2', 'multer', 'ejs'이다. 

 제작 파일들의 구성은 아래와 같다.

 

  • app.js
    • 모듈을 불러오고, 서버의 인스턴스를 생성, 대기 등 서버 생성의 기본적인 부분을 넣는다.
  • page 폴더
    • ejs 파일을 등록한다.
  • models 폴더
    • mysql과 연결하여 동작할 함수들을 기입한다.
  • controllers 폴더
    • models와 routers를 연결시켜주는 메서드를 작성한다.
  • public 폴더
    • css, 이미지 파일 등인 정적인 파일을 넣어준다.
  • routes 폴더
    • 페이지를 렌더하거나, 페이지에서 작업을 받아 controller로 보내주는 등의 router 작업을 수행한다.
  • upload 폴더
    • 글을 작성할 때 등록하는 이미지 파일을 저장한다.

 아래에는 각 폴더마다 주요 기능들이나, 기억해두어야 할 사항들 위주로 기입하고자 한다.

 

models

# config.js

  •  Mysql 연결을 할 때 promise 객체를 사용하여 createPool 메소드를 사용하고자 하기 때문에 아래처럼 선언.
const mysql2 = require("mysql2/promise");
  • Mysql을 쿼리문을 병렬적으로 처리할 수 있는 createPool메소드로 연결하고, 속성에 다중쿼리문을 쉽게 작성할 수 있도록 Multiple Statements을 true로 설정한다.
const mysql = mysql2.createPool({
    user : "root",
    password : "000000",
    multipleStatements : true,
    database : "toy_postPage"
})

 

# posts.js

  • 게시글에 대한 동작을 기입할 js파일이다.

 

  • Mysql 데이터베이스에 게시글에 대한 테이블 추가, 수정, 삭제 등을 위해 Mysql연결을 관리하는 config.js의 모듈을 불러온다.
const mysql = require("./config");
  • 게시글에 대한 동작이 작동하는 함수들을 posts에 담아 모듈화하여 exports한다.
const posts = {
    // 게시글 테이블을 생성해주는 함수
    initPostTable : async function(){
		..... },
    // 게시글 리스트를 조회하는 함수
    ViewPostAll : async function(){
		..... },
    // 게시글을 추가 해주는 메소드
    Insert : async function(title, content, image){
		..... },
    // 글을 선택했을 때 글 하나를 보여주는 함수
    SelectPost : async function(postIndex){
		..... },
    // 글을 수정하는 메소드
    Update : async function(postIndex, title, content){
		..... },
    // 글을 삭제하는 메소드
    Delete : async function(postIndex){
		..... },
    // 댓글 등록 메소드
    Comment : async function(postIndex, comment){
		..... },
    // 좋아요 기능 메소드
    Likes : async function(postIndex){
		..... }}
posts.initPostTable();

//--------- 게시글에 대한 기능이 담겨있는 객체 ------------------------------------------------------
module.exports = posts;
//-------------------------------------------------------------------------------------------------

 이 중, 테이블 생성 함수, 게시글 추가, 게시글 삭제, 게시글 좋아요 함수를 설명하고자 한다.

테이블 생성

  • try~catch문을 사용하여, posts 테이블이 있다면, 해당 테이블을 불러온다. 테이블이 없다면 생성을 진행한다.
  • posts의 컬럼은 postIndex, title, content, likes, comment, image로 구성된다.
    • postIndex: 게시글의 글번호를 나타내는 항목이다. 고유 값을 가지고 있는 항목이다.
    • title: 게시글의 제목을 기입할 항목이다.
    • content: 게시글의 내용을 기입할 항목이다.
    • comment: 댓글의 내용을 기입할 항목이다.
    • likes:
      • 게시글의 좋아요 수를 기입할 항목이다.
      • 이슈❗원래는 'like'로 작업했다가 쿼리문 'like'가 쿼리문 예약어라 에러가 났었다... 쿼리문 예약어는 컬럼명으로 사용할 수 없으니 제작 시, 주의하도록 하자!
    • image: 사진의 경로를 기입할 항목이다.
// 게시글 테이블을 생성해주는 함수
initPostTable : async function(){
    try {
        const[result] = await mysql.query("SELECT * FROM posts");
        console.log(result);
    } catch (error) {
        await mysql.query("CREATE TABLE posts(postIndex INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(20), content VARCHAR(300), likes VARCHAR(10), comment VARCHAR(300), image VARCHAR(100))")
    }}

게시글 추가

  • 게시글을 추가해주는 메소드이다. controller에서 매개변수로 각 값들을 받아 데이터베이스에 넣어 추가한다.
  • likes같은 경우, 처음 글을 생성할 때 좋아요 갯수는 항상 '0'이므로 고정값을 넣어준다.
// 게시글을 추가 해주는 메소드
Insert : async function(title, content, image){
    try {
        await mysql.query("INSERT INTO posts (title, content, likes, image) VALUES (?,?,?,?)",[title, content, 0, image]);
        console.log("(models-posts) 글 추가 완료!")
    } catch (error) {
        console.log("(models-posts) 글 추가 에러남!")
        
    }}

게시글 삭제

  • 게시글을 삭제하는 메소드이다. controller에서 매개변수로 게시글의 고정 값을 받아, 해당 값과 일치하는 게시글을 삭제한다.
  • 삭제 후, 글 번호는 다시 1부터 오름차순으로 정렬되어야 하므로 다중쿼리문을 이용하여 삭제와 글번호 업데이트를 동시에 진행한다.
Delete : async function(postIndex){
    try {
        await mysql.query("DELETE FROM posts WHERE postIndex=?; SET @CNT = 0; UPDATE posts SET posts.postIndex = @CNT:=@CNT+1; ALTER TABLE posts AUTO_INCREMENT = 0;",[postIndex]);
        console.log("게시글 삭제 완료");
    } catch (error) {
        console.log("(models) 게시글 삭제 에러남!");
        
    }}

게시글 좋아요

  • 게시글 좋아요의 값을 1씩 증가시키는 메소드이다.
// 좋아요 기능 메소드
Likes : async function(postIndex){
    try {
        await mysql.query("UPDATE posts SET likes = likes + 1 WHERE postIndex = ?", [postIndex]);
    } catch (error) {
        console.log("(models) 좋아요 버튼 에러!")
    }
}

 

# users.js

  • 회원가입 시, 유저의 테이블에 값을 넣어주고
  • 로그인 시도 시, 유저의 테이블 안에서 입력한 값을 찾아주는 메소드가 작성되어 있다.

이 중, 로그인 시도 시 테이블에서 값을 찾아주는 메소드를 설명하고자 한다.

로그인 하는 메소드

  • controller에서 매개변수로 입력한 값을 받아와 users 테이블에서 저장되어있는 값을 비교한다.
  • 비교한 값을 배열로 담아 해당 배열의 0번 index를 반환한다.
    • 반환 값이 undefined면 테이블에 저장된 값과 일치된 계정이 없다는 뜻이고
    • 반환 값이 있으면 테이블에 저장된 값과 일치된 값이 있다는 뜻으로 해당 반환값으로 로그인 성공여부를 설정할 수 있다.
// 로그인 하는 메소드
Login : async function(id,pw){
    try {
        const [result] = await mysql.query("SELECT * FROM users WHERE id = ? AND pw = ?",[id,pw]);
        return result[0];
    } catch (error) {
        console.log(error);
        console.log("(models) 로그인 에러!");
    }
}

 

# index.js

  • index.js는 다른 js파일에서 require시 경로를 파일명 까지만 적어줘도 자동으로 선택되는 파일이다.
  • 위에서 작성된 posts.js와 users.js의 모듈을 불러와서 한 번에 모듈화시켜 exports해서 좀 더 편리하게 사용한다. 
const posts = require("./posts");
const users = require("./users");

module.exports = {posts, users};

 

 

controllers

# controllers.js

  • model과 router를 연결시켜주는 메소드를 작성한다.

이 중, 게시글 등록 메소드와 조회 메소드를 설명하고자 한다.

 

게시글 등록 메소드 (이미지파일)

  • 게시글 등록 시, ejs에서 받아온 데이터를 매개변수로 받아와 model로 보내준다.
  • image의 경우, 파일의 이름을 받아서 경로에 기입하여야 하므로, ' file.filename '을 작성하여 이미지 파일의 이름을 불러온다.
// 게시글 등록 메소드
exports.Insert = async function(q,s){
    const { title, content } = q.body;
    const image = q.file.filename;
    try {
        console.log("컨트롤러",image)
        await posts.Insert(title, content, image);
    } catch (error) {
        console.log("(controller) 글 추가 에러남!")
    }}

게시글 조회 메소드

  • 데이터베이스에서 postIndex 값을 받아와 model로 보내주고 해당 모델에서 리턴받은 값을 router에 다시 리턴값으로 내보내 게시글을 선택했을 때, url에 해당 게시글의 번호를 입력하여 페이지가 렌더되도록 한다.
// 글 하나 조회 메서드
exports.SelectPost = async function(q,s){
    const { postIndex } = q.params;
    try {
        const data = await posts.SelectPost(postIndex);
        return data;
    } catch (error) {
        console.log("(controller) 글 한개 조회 에러")
    }
}

 

 

routers

# router.js

require로 controller.js에서 작성된 메소드들 가져오기

  • controller.js에서 작성된 메소드들을 사용하기 위해서 require로 불러온다.
const { ViewPostAll, Insert, SelectPost, Update, Delete, Comment, Likes, Signup, Login } = require("../controllers/controllers");

 

게시글 추가 처리 / multer 모듈을 사용하여 이미지 파일을 등록한다.

  • 게시글을 추가할 때, 게시글의 제목, 내용, 이미지파일을 같이 등록하도록 구성하였다.
  • 게시글의 제목과 내용은 text 타입의 input과 textarea로 값을 받았고, 이미지 파일은 file 타입의 input으로 값을 받았다.
  • 이미지의 경우, 데이터베이스에 이미지 자체를 업로드 하기에 용량이 너무 크고, 올바른 방법도 아니기 때문에 업로드한 이미지를 작업하는 하드 내에 저장하고, 저장된 이미지의 경로를 불러오도록 한다.

multer를 사용하여 이미지 파일을 업로드하여 게시글을 등록해보자!

  • ' npm i multer '를 입력하여 외부모듈 multer를 받는다.
  • 외부 모듈 multer를 선언하여 사용할 수 있도록 한다.
const multer = require("multer");
  • multer에는 storage 속성이 있어, 등록한 이미지 파일의 저장경로와 저장이름을 설정할 수 있다.
    • diskStorage(): 등록한 이미지 파일을 하드에 저장하는 용도의 함수.
    • destination: 등록한 파일을 저장할 하드 경로를 설정.
    • filename: 등록한 파일이 어떤 이름으로 저장될지 설정
    • path.extname: 해당 매개변수의 파일 확장자명을 리턴한다.
// 게시글 추가 요청을 처리-----------------------------------------------------------------

// 이미지 파일의 저장경로와 저장이름을 설정
var storage = multer.diskStorage({
	// 저장 경로 설정
    destination : function(req, file, cb){
    	// 현재 주소(__dirname)의 바깥 파일(..)로 나가 upload파일에 저장하도록 한다.
        cb(null, (path.join(__dirname, '..', "upload")))
    },
    // 저장 이름 설정
    filename : function(req, file, cb){
    	// 등록 파일의 확장자명을 ext에 담는다.
        const ext = path.extname(file.originalname);
        // 파일의 원래이름_현재시간.확장자
        cb(null, file.originalname + "_" + Date.now() + ext);
    }
});


// multer에 storage속성이 있어, 저장경로와 저장이름을 저장할 수 있다.
// storage속성에 var storage를 설정한다.
var upload = multer({storage: storage});

// router에서 post로 요청을 받으면 upload.singe('image') 미들웨어가 실행되고 그 후에 async 함수가 실행된다.
router.post('/insert', upload.single('image'), async(req,res)=>{
    try {
        await Insert(req,res);
        res.redirect("/list");
    } catch (error) {
        console.log(error)
        console.log("(router) 글 추가하다 에러!")
    }
})

 

게시글 댓글 처리

  • 게시글 댓글을 처리 후, redirect되는 경로를 게시글 상세로 지정하려면, await에서 postIndex값을 리턴받아 적용시킨다.
// 게시글 댓글 처리
router.post('/detail/:postIndex', async (req,res)=>{
    try {
    	// data에는 posts 데이터베이스에서 postIndex값을 리턴받는다.
        const data = await Comment(req,res);
        console.log(data);
        // 리턴받은 postIndex값을 넣어 댓글처리한 게시물의 상세페이지로 이동하도록 한다.
        res.redirect('/list/detail/'+data);
    } catch (error) {
        console.log("(router) 댓글 등록 에러!");
        console.log(error);
    }
})

 

 

app.js

app.js에서는 하단의 작업들을 수행하였다.

  • express, path, multer 모듈을 불러온다.
  • 서버 인스턴스를 생성한다.
  • view 엔진으로 보여 줄 파일의 경로를 설정한다.
//view엔진으로 보여줄 파일들의 경로를 설정한다.
app.set("views", path.join(__dirname, "page"));

// view 엔진으로 ejs를 사용할 수 있게 설정한다.
app.set("view engine", "ejs")
  • post 요청 시, input의 value 값을 전달하기 위한 미들웨어를 추가한다.
  • 정적 파일의 기본경로를 설정하기 위한 미들웨어를 추가한다.
app.use("/css",express.static(path.join(__dirname,"public")));
app.use("/image",express.static(path.join(__dirname,"upload")));
  • 라우터를 설정한다.
  • 서버를 대기시킨다.

 

 

 작업은 이와 같은 과정들을 거쳤고, css와 이미지파일이 있는 public폴더나 페이지를 구성하고 있는 ejs파일이 있는 page 폴더는 별도로 설명을 하진 않겠다. 페이지 디자인은 어떤 방식으로 진행할지 고민하다가 트위터를 참고하여 제작하고자 하였다. (트위터를 참고했기 때문에 게시글에 이미지를 넣고 싶은 욕심이 커졌고, multer 메소드를 쓰게 된 것이다.)

 

 

 

 


결과물

 

게시글 메인 페이지

 

게시글 상세페이지_좋아요 / 수정 / 댓글

 

게시글 생성 / 삭제

 

 

728x90