본문 바로가기

블록체인_9기/💚 Node.js

29강(과제X)_230508_Node.js(MVC 패턴으로 게시판 페이지 제작)

728x90

 

 

 

 


MVC 패턴

  • MVC ( Model-View-Controller)
  • 사용자 인터페이스, 데이터 및 논리 제어를 구현하는데 널리 사용되는 소프트웨어 디자인 패턴이다.
  • MVC 패턴은 많이 사용하고, 표준이며, 기본적인 디자인 패턴이다.
  • MVC 패턴으로 작업하는 이유는 유지보수와 확장성을 고려해서 개발할 수 있다.
  • 협업 시, 코드의 표준화에도 용이하다.
  • MVC 소프트웨어 디자인 패턴의 세 가지 부분
    • Model
      • 데이터와 비즈니스 로직을 관리한다.
      • 글 선택, 글 작성 등등의 기능들
      • 어플리케이션의 동작을 관리하는 폴더이다.
    • View
      • 레이아웃과 화면을 처리한다.
      • 사용자가 볼 수 있는 화면의 데이터를 표시해주는 역할
    • Controller
      • 명령을 모델과 뷰 부분으로 라우팅한다.
      • model과 view의 사이에서 동작을 제어해주는 역할
      • model의 상태를 가지고 view에 반영해주는 것이다.

출처: https://velog.io/@khy226/MVC-%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80

 

 

MVC 패턴을 기반으로 게시판 페이지를 제작하고자 한다.
제작 순서는 다음과 같다.
(무조건적인 순서는 아니다. 해당 작업의 순서를 나타내고자 함이다.)

app.js > models - config.js > models - posts.js > models - index.js > controllers - posts.js > routes - posts.js > page - main.ejs > page - detail.ejs > page - insert.ejs > public - main.css > page - edit.ejs 


< 폴더 경로 >


 


서버단을 관리하는 app.js

1. 내장모듈 express, path 라우터를 가져온다.

// 내장모듈 express를 가져옴
const express = require("express");
// 게시글의 라우터만 모아둔 js파일을 가져옴
const postRoute = require("./routes/posts")
// 내장모듈 path를 가져옴
const path = require("path");

2. 서버 인스턴스를 생성한다.

//서버 인스턴스 생성
const app = express();

 

3. view엔진을 설정한다.

// view 엔진으로 보여줄 파일들의 경로 설정
app.set("views", path.join(__dirname, "page"));

// view 엔진으로 ejs를 사용하도록 설정
app.set("view engine", "ejs");

// page 폴더 안의 ejs 파일들을 보여줄 것이다.

4. body객체와 정적인 파일을 사용하기 위한 미들웨어 추가

// body 객체 사용하기 위해 미들웨어 추가
// 깊은 객체를 사용할 건가? 'extended : false == 사용안함'. 사용할 일이 없다.
app.use(express.urlencoded({extended : false}));



// 정적인 파일을 사용하기 위해 미들웨어 추가
// 정적인 파일을 모아놓은 경로를 public 폴더로 지정
app.use(express.static(path.join(__dirname,"public")));

// 이렇게 지정한 경우에는 ejs단에 '/css/파일명'으로 접근을 하면 된다.
// 정적 파일 경로도 나눌 수 있다.
app.use("/css",express.static(path.join(__dirname,"public")))

5. 루트경로의 변경

  • ' /posts/ ~ ' 이 뒷 부분의 경로로 입력해야 ' routes - posts.js ' 의 경로로 이동할 수 있다.
    • 이렇게 해야  routes - posts.js의 로직들을 사용할 수 있어요.....아마도....?
// postRoute객체에 get 메서드로 / 루트경로 지정했을 때
// "/posts"이 경로도 추가되어 라우팅된다.
// 게시글은 /posts 붙여야 루트경로로 요청이 된다.
app.use("/posts", postRoute);

6. 포트를 생성하고 서버를 대기상태로 만들어준다.

// 서버가 열릴 포트번호 지정
const PORT = 8000;

// 서버 대기
app.listen(PORT,()=>{
    console.log("서버 잘 열림")
})

 

 

 


models

  • 데이터와 비즈니스 로직을 관리할 'models' 폴더 생성

 

1. config.js - Mysql에 연결하는 작업 진행

  • 먼저 mysql에 연결을 진행해, 데이터베이스에 저장을 할 수 있도록 준비한다.

1. 외부모듈 'Mysql2' 선언

// createPool을 사용하기 위해 promise api 선언하여 사용
// createPool를 생성하고 관리할 수 있는 메소드이다.
const mysql2 = require("mysql2/promise");

2. createPool 메소드로 Mysql 연결

  • createConnection
    • 우리가 이전에 사용했던 방식
    • 콜백 함수 기반이며, promise를 반환하지 않았다.
    • 기본적인 연결을 해주는 메소드이며 테스트할 때 사용한다.
    • 단일 클라이언트 접속에 용이하다.
  • createPool
  • promise 객체를 반환한다.
  • 많은 클라이언트가 데이터베이스와 통신할 때, 요청이 많이 들어와도 성능이 유지되고 여러 개의 요청을 처리하는데 좋다.
  • async, await의 원활한 동작이 가능하다.
const mysql = mysql2.createPool({
    user : "root",
    password : "000000",
    // 다중 쿼리문 사용할 예정
    // 다중 쿼리문 설정 - 다른 쿼리문을 실행해서 글 번호를 다시 순서대로 적용하도록함
    multipleStatements : true,
    database : "test1"
})
  • multipleStatements
    • 해당 쿼리문에 true 값을 주면, 세미콜론(;)으로 이어진 여러개의 쿼리문을 한꺼번에 적용시킬 수 있다.

3. getConnection 연결 확인 메서드 선언

  • getConnection: 자바 프로그램과 데이터베이스를 네트워크상에서 연결해주는 메소드이다.
//연결 확인 메서드
mysql.getConnection((err,res)=>{
    // 연결이 정상적으로 되지 않으면
    console.log(err);
    // 정상적으로 연결되면 res객체에 연결 인스턴스가 넘어온다.
})

4. 외부파일에 mysql의 내용을 내보낸다.

// 외부파일에서 mysql의 내용을 받을 수 있도록 내보낸다.
module.exports = mysql;

 

2. posts.js - 게시판의 기능들을 작업

 

1. config.js의 mysql객체가 담고있는 데이터베이스를 가져온다.

const mysql = require("./config")

 

2. 테이블을 초기화 해주는 함수를 선언

  • 게시판의 기능들이 담길 객체 posts 선언
const posts = {
    // 테이블을 초기화 해주는 함수
    initTable : async function(){ ... },
    // 글의 리스트를 조회하는 함수
    viewPostAll : async function(){ ... },
    // 글을 선택했을 때 글 하나를 보여줄 함수
    selectPost : async function(id){ ... },
    // 글을 추가 해주는 메서드
    insert : async function(title, content){ ... },
    // 글 수정하는 메서드 
    update : async function(id, title, content){ ... },
    // 글을 삭제하는 메소드
    delete : async function(id){ ... }
};
  • async function
    • 비동기 함수이다.
    • promise 코드를 보다 직관적으로 나타낼 수 있다.
// 테이블을 초기화 해주는 함수
initTable : async function(){
    try {
        // 배열의 스프레드 연산자. 0 1 2 3 4 이런 식으로 순서대로 담긴다.
        const[result] = await mysql.query("SELECT * FROM posts");
        console.log(result)
    } catch (error) {
    	// 테이블이 없어 에러가 나면 posts 테이블을 생성한다.
        await mysql.query("CREATE TABLE posts(id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(20), content VARCHAR(100))")
    }
}

 

3. 글의 리스트를 조회하는 함수를 선언

// 글의 리스트를 조회하는 함수
viewPostAll : async function(){
    try {
        const [result] = await mysql.query('SELECT * FROM posts');
        // posts 테이블의 데이터 전부 조회
        return result;
    } catch (error) {
        console.log("글 전체 조회 에러남");
    }
}

 

4. 글을 선택할 때 하나의 글을 보여주는 함수 선언

selectPost : async function(id){
    try {
        const [result] = await mysql.query("SELECT * FROM posts WHERE id = ?", [id]);
        console.log("선택한 게시글 : ",result[0]);
        return result[0];
    } catch (error) {
        console.log("글 선택 조회 에러남")
    }
}

 

5. 글을 추가해주는 함수 선언

// 글을 추가 해주는 메서드
insert : async function(title, content){
    try {
        await mysql.query("INSERT INTO posts (title, content) VALUES (?,?)",[title, content]);
        console.log("글 추가 완료~");
    } catch (error) {
        console.log("글 추가 에러남!");
    }
}

 

6. 글을 수정하는 함수 선언

// 글 수정하는 메서드 
update : async function(id, title, content){
    try {
        await mysql.query("UPDATE posts SET title = ?, content = ? WHERE id = ?",[title, content, id]);
        console.log("게시글 수정 완료!")
    } catch (error) {
        console.log(error);
    }
}

 

7. 글을 삭제하는 함수 선언

// 글을 삭제하는 메소드
delete : async function(id){
    try {
        await mysql.query("DELETE FROM posts WHERE id=?; SET @CNT = 0; UPDATE posts SET posts.id = @CNT:=@CNT+1; ALTER TABLE posts AUTO_INCREMENT = 0;",[id]);
        console.log("게시글 삭제 완료");
    } catch (error) {
        console.log("게시글 삭제 에러남!")
    }
}

 

8. 게시판의 기능(2번 ~ 7번)을 담은 객체를 exports를 사용하여 내보낸다.

module.exports = posts;

 

3. index.js - config.js와 posts.js의 모듈을 받는 js파일

  • index.js를 생성한 이유는 require로 경로를 지정할 때, 경로의 파일명이 아닌 폴더명 까지만 입력하면 기본적으로 ' index.js '를 찾기 때문이다.
const posts = require("./posts");
const user = require("./users");

module.exports = {posts, user};
  • 해당 모듈을 require로 불러올 때, 게시글에 대한 정보가 담긴 post.js와 유저의 정보가 담긴 user.js를 편리하게 불러오기 위해 작성한 js 파일이다....?

 

 


controllers

1. posts 객체에 models 폴더의 index.js를 불러온다.

  • index.js
    • models - posts.js   (해당 파일을 불러온다.)
    • models - users.js

 

2. 전체 글 조회하는 메소드

// 전체 글 조회 메소드
exports.ViewPostAll = async function(q,s){
    try {
        const data = await posts.viewPostAll();
        return data;
    } catch (error) {
        console.log("전체 글 조회. 컨트롤러에서 에러남!");
    }
}

 

3. 글 하나 조회하는 메소드

// 글 하나 조회 메서드
exports.SelectPost = async function(q,s){
    // q <- 요청 객체를 메개변수로 전달해 줄 예정
    const { id } = q.params;
    try {
        const data = await posts.selectPost(id);
        return data;
    } catch (error) {
        console.log("글 한개 조회. 컨트롤러 에러남!");
    }
}

 

4. 게시글 등록 메소드

// 게시글 등록 메소드
exports.Insert = async function(q,s){
    // 요청 객체를 매개변수로 전달해줄 예정
    const{ title, content } = q.body
    try {
        await posts.insert(title, content);
    } catch (error) {
        console.log("글 추가. 컨트롤러 에러남!")
    }
}

 

5. 게시글 수정 메소드

// 게시글 수정 메소드
exports.Update = async function(q,s){
    const { id } = q.params;
    const { title, content } = q.body;
    try {
        await posts.update(id, title, content);
    } catch (error) {
        console.log("글 수정. 컨트롤러 에러남!");
    }
}

 

6. 게시글 삭제 메소드

exports.Delete = async function(q,s){
    const { id } = q.params;
    try {
        await posts.delete(id);
    } catch (error) {
        console.log("글 삭제. 컨트롤러 에러남!");
    }
}

 

 

 


routers

 

1. express 모듈을 불러오고, Router 메소드를 선언한다.

  • Router 메소드
    • express.Router 클래스를 사용하면 모듈식 마운팅 가능한 핸들러를 작성할 수 있습니다.
    • 라우팅을 관리할 수 있게 도와주는 메소드
    • 라우터를 나눠서 관리할 수 있다.
const express = require("express");

// Router 메소드: 라우팅을 관리할 수 있게 도와주는 메소드
const router = express.Router();

 

2. controller - posts.js 에서 작성한 데이터 흐름 로직을 불러온다.

// 컨트롤러에 작성한 내용을 가져오자.
const {ViewPostAll, SelectPost, Insert, Update, Delete} = require("../controllers/posts");

 

3. 게시글 메인 페이지

  • ' controller - posts.js '에서 작성한 함수를 실행하고, ' main.ejs '에 결과값을 전달하여 ' page - main.ejs '에 출력한다.
// 게시글 메인페이지
router.get('/', async(req,res)=>{
    try {
        const data = await ViewPostAll(req,res);
        res.render('main', {data});
    } catch (error) {
        console.log("게시글 리스트 화면 그리다 에러남!")
    }
})

 

4. 게시글 상세 페이지

  • ' controller - posts.js '에서 작성한 함수를 실행하고, 'detail.ejs '에 결과값을 전달하여 ' page - detail.ejs '에 출력한다.
// 게시글 상세 페이지
router.get('/view/:id', async (req,res)=>{
    try {
        const data = await SelectPost(req,res);
        res.render('detail',{data})
    } catch (error) {
        console.log("게시글 상세페이지 그리다 에러남!")
    }
})

5. 게시글 추가 페이지

1. 게시글 추가 페이지를 출력한다.

// 게시글 추가 페이지
router.get ('/insert',(req,res)=>{
    res.render('insert');
})

2. 게시글 추가 요청이 들어오면 동작하는 메소드?

  • ' controller - posts.js '에서 작성한 함수를 실행하고, 'insert.ejs '에 결과값을 전달하여 ' page - insert.ejs '에 출력한다.
router.post('/insert', async (req,res)=>{
    try {
        await Insert(req,res);
        res.redirect("/posts");
    } catch (error) {
        console.log("글 추가 하다가 에러남. 여기 프론트임");
    }
})

 

6. 게시글 수정 페이지

1. 게시글 수정 페이지를 출력한다.

  • ' controller - posts.js '에서 작성한 함수를 실행하고, 'edit.ejs '에 결과값을 전달하여 ' page - edit.ejs '에 출력한다.
//게시글 수정 페이지
router.get('/edit/:id', async (req,res)=>{
    try {
        const data = await SelectPost(req,res);
        res.render('edit',{data});
    } catch (error) {
        console.log("수정 페이지 그리다 에러남!")
    }
})

2. 게시글 수정 요청이 들어오면 동작하는 메소드?

  • url의 ( ~ /posts/edit/?)에서 ?에 담겨있는 값이 id키의 값으로 대입된다.
  • 해당 id값의 게시물을 찾아 사용자가 브라우저에 입력한 title, content 의 input 벨류 값으로 수정.
  • ' controller - posts.js '에서 작성한 함수를 실행하고, 'detail.ejs '에 결과값을 전달하여 ' page - detail.ejs '에 출력한다.
// 게시글 수정 버튼 눌러서 수정
router.post('/edit/:id',async (req,res)=>{
    try {
        await Update(req,res);
        res.redirect('/posts');
    } catch (error) {
        console.log("게시글 수정하다 에러남. 프론트임!");
    }
})

 

7. 게시글 삭제 처리

  • ' controller - posts.js '에서 작성한 함수를 실행하여 해당 게시글 삭제 후,  ' page - main.ejs '에 출력한다.
// 게시글 삭제 처리
router.get('/delete/:id', async (req,res)=>{
    try {
        await Delete(req,res);
        res.redirect('/posts');
    } catch (error) {
        console.log("게시글 삭제하다가 에러남")
    }
})

 

8. 라우터 메소드를 exports로 내보낸다.

module.exports = router;

 

 

 


page

 

1. main.ejs

<!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>
    <link rel="stylesheet" href="/main.css">
</head>
<body>
    <h1>메인 페이지</h1>
    <table>
        <!--클릭한 게시글의 id값을 받아 해당 게시글의 주소로 이동-->
        <a href="/posts/insert">추가 페이지</a>
        <tr>
            <th>번호</th>
            <th>타이틀</th>
            <th>컨텐츠</th>
            <th>삭제</th>
            <th>수정</th>
        </tr>
        <% data.forEach((el,index) => { %>
            <tr>
                <td><%= el.id %></td>
                <td><a href="/posts/view/<%= el.id %>"><%= el.title %></a></td>
                <td><%= el.content %></td>
                <td><a href="/posts/delete/<%= el.id %>">삭제</a></td>
                <td><a href="/posts/edit/<%= el.id %>">수정</a></td>
            </tr>
        <% }); %>
    </table>
</body>
</html>

2. detail.ejs

<!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>
    <label for="">id</label>
    <input type="text" value="<%= data.id %>" disabled />
    <label for="">이름</label>
    <input type="text" value="<%= data.title %>" disabled />
    <label for="">내용</label>
    <input type="text" value="<%= data.content %>" disabled />
    
</body>
</html>

3. insert.ejs

<!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>
    <form method="post">
        <label for="">제목</label>
        <input type="text" name="title">
        <label for="">게시긓</label>
        <input type="text" name="content">
        <!-- {title : input value, content : input value} -->
        <button>추가</button>
    </form>
</body>
</html>

4. edit.ejs

<!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>
    <form method="post">
        <label for="">id</label>
        <input type="text" name="id" value="<%= data.id %>" disabled />
        <label for="">이름</label>
        <input type="text" name="title" value="<%= data.title %>">
        <label for="">내용</label>
        <input type="text" name="content" value="<%= data.content %>">
        <button>수정</button>
    </form>
</body>
</html>

 

 


public

 

1. main.css

table, th, td{
    border: 1px solid;
}

 

 

 


브라우저

 

메인 페이지

 

추가 페이지

 

상세 페이지

 

수정 페이지

 

삭제 

 

 

 

 

 

728x90