Node

[Node.js] MVC 패턴 사용해보기

YaluStar 2023. 3. 11. 00:35

안녕하세요.

이번에는 Node.js의 MVC 패턴에 대해서 알아보겠습니다.

 

1. MVC

  • Model View Controller
  • 소프트웨어 설계와 관련된 디자인 패턴
  • MVC 이용 웹 프레임워크
    • php
    • Django
    • Express
    • Angular 등

 

장점

  • 패턴들을 구분해 개발한다.
  • 유지보수가 용이하다.
  • 유연성이 높다.
  • 확장성이 높다.
  • 협업에 용이하다.

 

단점

  • 완벽한 의존성 분리가 어렵다.
  • 설계 단계가 복잡하다.
  • 설계 시간이 오래 걸린다.
  • 클래스가 많아진다.

 

 

2. 처음 폴더 및 파일 세팅

환경 : Node.js + MySQL

node6 말고 다른 이름으로 사용하셔도 됩니다.

Model

  • 데이터를 처리하는 부분

 

Views

  • UI 관련된 것을 처리하는 부분 (사용자에게 보이는 부분)

 

Controller

  • Views와 Model을 연결해 주는 부분

 

3. npm 설치

npm init —yes
npm install express
npm install ejs
npm install -g nodemon

 

최상단 index.js

const express = require('express');
const app = express();
const port = 8080;

app.set('view engine', 'ejs');
app.use(express.urlencoded({ extended: true}));
app.use(express.json());

app.use('/static', express.static(__dirname+'/static'));

// routes 폴더에서 필요한 정보를 가져온다.
// 뒤에 파일 이름을 작성하지 않으면 기본 값으로 index.js 파일을 가져온다.
// const router = require('./routes/index');
const router = require('./routes');

// localhost:8080
// localhost:8080/url주소
// 2개와 같이 입력이 되면 router로 이동을 시키겠다 라는 의미
// 미들웨어 등록을 통해 접속을 하게 되면 무조건 router를 들르게 된다.
app.use('/', router);

// 주소가 아니라 *로 입력이 되어 있는데, 위에서 정의해둔 router가 아닌 다른 주소를
// 입력하였을 경우 error 페이지를 보여주게 설정하는 방법이다.
// ('*')은 무조건 마지막에 작성을 해줘야 다른 주소들이 정상적으로 작동한다.
app.get('*', (req, res) => {
    // res.render('error');
    res.send('주소가 존재하지 않습니다. 다시 한 번 확인해주세요.');
});

app.listen(port, () => {
    console.log('server open : ', port);
});

[참고] 현 상태에서는 코드를 실행해도 Error 메시지가 출력된다.

  • 분리된 코드들이 전부 작성이 안되어 있기 때문에 아직 Error 메시지만 출력된다.
  • routes 폴더의 index.js와 controller 폴더의 Cmain.js 까지 작성하면 코드가 실행 가능하다.

 

 

Router 코드 설명

- 주소가 아니라 *로 입력이 되어 있는데, 위에서 정의해 둔 router가 아닌 다른 주소를 입력하였을 경우 error 페이지를 보여주게 설정하는 방법이다.

const router = require('./routes');
app.use('/', router);

// 주소가 아니라 *로 입력이 되어 있는데, 위에서 정의해둔 router가 아닌 다른 주소를
// 입력하였을 경우 error 페이지를 보여주게 설정하는 방법이다.
app.get('*', (req, res) => {
    res.render('error');
});

 

따로 지정을 안 한 주소를 입력하면 ‘Cannot Get /url’ 페이지가 나오는데 웹에 대하여 전혀 모르는 사람이 당황할 수 있기 때문에 사용한다.

여기서는 error.ejs 파일이 없어서 출력이 안되었다.

 

app.get(’*’) 코드의 res.render 대신에 res.send 코드로 아래와 같이 수정하고 실행한다.

app.get('*', (req, res) => {
    // res.render('error');
    res.send('주소가 존재하지 않습니다. 다시 한 번 확인해주세요.');
});

 

코드를 실행하고 없는 주소를 입력하면 다음과 같이 출력되는 것을 볼 수 있다.

 

routes 폴더

  • 경로를 controller와 연결 지어 설정할 수 있다.

 

routes/index.js

const express = require('express');
const controller = require('../controller/Cmain');
const router = express.Router();

// app.get 처럼 사용하지만 뒤에 함수는 요청에 따라서 달라야 하기 때문에 다르게 작성되는 것임
router.get('/', controller.main);

// 예시2개
// controller/Cmain.js 에서 설정을 해줬기 때문에 사용이 가능하다.
router.get('/test', controller.test);
router.post('/postForm', controller.post);


// 다른 파일에서 require() 함수를 이용해서
// 파일을 사용하고 싶은 경우에는 해당 파일에서는 module.exports 를 사용해줘야 한다.
module.exports = router;

 

model 폴더

  • DB에서 나오는 결과 값
exports.hello = function() {
    return 'helloTest';
		// 임시 값
}

// Arrow 방식으로 작성해도 상관 없음
// exports.hello = () => {
//     return 'hello';
// }


// 나중에 DB에서 값을 가져올 때 이렇게 배열로 가져올 수 있다는 예시
exports.test = function() {
    return [
        { id: 'a', name: '새싹'},
        { id: 'b', name: '용산'}
    ];
}

 

controller 폴더

  • 경로와 연결될 함수 내용을 정의한다.
  • 경로와 연결되는 함수이기에 request 객체와 response 객체를 사용할 수 있다.

 

controller/Cmain.js

const Test = require('../model/Test');
// model 폴더 사용을 위해 require 함수 사용

exports.main = (req, res) => {
    // res.send('hello');
    // send 말고 render 이용해서 views 폴더의 index.ejs 파일도 호출 가능
    // res.render('index');

    let hi = Test.hello();
    // hi 변수에 hello 함수의 return 값 helloTest 문자가 저장됨
    res.send(hi);
}

exports.test = (req, res) => {
    res.send('test');
}

exports.post = (req, res) => {
    res.send('post');
}

 

views 폴더

  • 실제 html 및 사용자에게 보이는 코드, 파일

views/index.ejs

<!DOCTYPE html>
<html lang="en, ko">
<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>MVC</title>
</head>
<body>
    안녕하세요 index.ejs 파일입니다.
</body>
</html>

 

실습코드

해당 실습 코드는 조회 + 등록만 가능합니다

수정, 삭제는 구현되어 있지 않습니다.

더보기

1. MVC 구조 만들어보기

 

 

 

 

 

 

DB - visitor 테이블 만들기

 

DB - visitor 테이블 데이터 넣기

 

 

Node.js - MySQL 연결

npm install mysql

package.json 파일에서 설치되었는지 확인

 

/model/Visitor.js

const mysql = require('mysql');

const cnn = mysql.createConnection({
    host: 'localhost',
    user: 'test',
    password: 'qwer1234',
    database: 'test'
});

// cb는 controller의 get_visitor 함수의 매개변수에 해당한다.
exports.get_visitor = (cb) => {
    let sql = 'select * from visitor';
    cnn.query(sql, (err, rows) => {
        if(err) throw err;

        console.log('visitors : ', rows);
        cb(rows);
    });
}

 

*굳이 콜백 함수로 넘기는 이유

  • query문 마지막에 return rows; 이용해서 값을 넘긴 후에 controller에서 렌더링을 해주면 되지 않냐라는 의문이 생길 수 있지만
  • query문은 다른 명령문에 비해서 시간이 오래 걸리고 비동기식으로 인해서 원하는 값이 제대로 출력이 되지 않는 경우가 발생할 수 있습니다.
  • 그래서 query 함수에다가 콜백 함수를 이용하여 비동기식을 사용해도 값이 정상적으로 출력되기 위하여 사용합니다.

 

#root 계정 사용 불가

  • mysql 특성으로 인한 root 계정 사용 불가로 새로운 계정 생성 필요
  • 새로운 계정 생성 후 권한 설정 필요

 

/contorller/Cvisitor.js

const Visitor = require('../model/Visitor');

exports.visitor = (req, res) => {
    Visitor.get_visitor(function(result) {
        console.log(result);
        res.render('visitor', { data: result});
    });
}

 

최상단 index.js

const express = require('express');
const app = express();
const port = 8080;

app.set('view engine', 'ejs');
app.use(express.urlencoded({ extended: true}));
app.use(express.json());

app.use('/static', express.static(__dirname+'/static'));


const router = require('./routes');

app.use('/visitor', router);

app.get('*', (req, res) => {
    res.send('주소를 확인해 주세요.');
});


app.listen(port, () => {
    console.log('server open : ', port);
});

 

/routes/index.js

const express = require('express');
const controller = require('../controller/Cvisitor');
const router = express.Router();

router.get('/', controller.visitor);

module.exports = router;

 

/views/visitor.ejs

<!DOCTYPE html>
<html lang="en, ko">
<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>MVC 구조 만들기 - 2</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
    <style>
        table, th, td {
            border: 1px solid black;
        }
    </style>
    <script>
        function register() {
            const form = document.getElementById('form_register');
            let data = {
                name: form.name.value,
                comment: form.comment.value
            };
            axios({
                method: 'post',
                url: '/visitor/register',
                data: data
            })
            .then((response) => {
                // $('table').append(`
                $('#mainTable').append(`
                <tr>
                    <td>${response.data}</td>
                    <td>${data.name}</td>
                    <td>${data.comment}</td>
                    <td><button type="button">수정</button></td>
                    <td><button type="button">삭제</button></td>
                </tr>
                `)

                // JavaScript 버전
                const tr = document.createElement('tr');
                tr.innerHTML = `
                        <td>${response.data}</td>
                        <td>${data.name}</td>
                        <td>${data.comment}</td>
                        <td><button type="button">수정</button></td>
                        <td><button type="button">삭제</button></td>
                        `;
                document.getElementById('testTable').append(tr);
            });
        }
    </script>
</head>
<body>
    <div>
        <form id="form_register">
        <fieldset>
            <legend>방명록 등록</legend>
            이름 : <input type="text" placeholder="사용자 이름" name="name"><br>
            방명록 : <input type="text" placeholder="방명록" name="comment"><br>
            <div class="register-btn">
                <button type="button" onclick="register();">등록</button>
            </div>
            </fieldset>
        </form>
    </div>
    <div>
        <table id='mainTable' cellspacing="0" cellpadding="10" style="margin-top: 30px;">
            <tr>
                <th>ID</th>
                <th>작성자</th>
                <th>방명록</th>
                <th>수정</th>
                <th>삭제</th>
            </tr>
            <% for(let i = 0; i < data.length; i++) { %>
                <tr>
                    <td><%= data[i].id %></td>
                    <td><%= data[i].name %></td>
                    <td><%= data[i].comment %></td>
                    <td><button type="button">수정</button></td>
                    <td><button type="button">삭제</button></td>
                </tr>
            <% } %>
        </table>
        <div>
            <table id='testTable'>
            </table>
        </div>
    </div>
</body>
</html>

 

실행 결과 - html

 

실행 결과 - 콘솔 창

 

 

 

 

 

등록 기능 추가 (기존 파일 수정)

 /model/Visitor.js

const mysql = require('mysql');

const cnn = mysql.createConnection({
    host: 'localhost',
    user: 'test',
    password: 'qwer1234',
    database: 'test'
});

// cb는 controller의 get_visitor 함수의 매개변수에 해당한다.
exports.get_visitor = (cb) => {
    let sql = 'select * from visitor';
    cnn.query(sql, (err, rows) => {
        if(err) throw err;

        console.log('visitors : ', rows);
        cb(rows);
    });
}

exports.register_visitor = (info, cb) => {
    let sql = `insert into visitor (name, comment) values ('${info.name}', '${info.comment}')`;
    cnn.query(sql, (err, result) => {
        if (err) throw err;

        console.log('insert result: ', result);
        console.log('insert result: ', result.insertId);
        cb(result.insertId);
    });
}

 

/contorller/Cvisitor.js

const Visitor = require('../model/Visitor');

exports.visitor = (req, res) => {
    Visitor.get_visitor(function(result) {
        console.log(result);
        res.render('visitor', { data: result});
    });
}

exports.register = (req, res) => {
    Visitor.register_visitor(req.body, function(id) {
        console.log(id);
        res.send(String(id));
    });
}

 

/routes.index.js

const express = require('express');
const controller = require('../controller/Cvisitor');
const router = express.Router();

router.get('/', controller.visitor);

router.post('/register', controller.register);

module.exports = router;

 

이상으로 Node.js에서 MVC 패턴 사용해 보기였습니다.

감사합니다.

 

반응형