웹 개발

[포스코X코딩온] 모델 뷰 컨트롤러 MVC

끊임없이 성장중인 개발자 2023. 12. 1. 19:35
728x90
반응형

포스팅 주제

  • MVC

MVC란?

  • MVC (Model View Controller)
  • 소프트웨어 설계와 관련된 디자인 패턴
    • 상황에 따라 자주 쓰이는 설계 방법을 정리한 코딩 방법론!! (이미 같은 상황을 겪은 사람들이 정리한,,)
  • MVC 이용 웹 프라임워크
    • PHP
    • Django
    • express
    • Angular
    • .... etc

 

 

MVC  흐름 )

 

 

MVC 모델-뷰-컨트롤이란 이름에서 볼 수 이겠지만, 말 그대로 Model, View, Controller를 컨트롤하는 것으로

 

Model

  • 데이터를 처리하는 파트
  • DB 데이터

View

  • UI 관련된 것을 처리하는 부분 (사용자에게 보여지는 부분)
  • HTML/CSS 등

Controller

  • View와 Model을 연결해주는 부분
  • 사용자가 GUI화면을 통해 데이터를 읽기, 쓰기, 지우기를 할 수 있도록 제어

 

 

실습을 통해서 해당 구조를 같이 파악해 봅시다!

실습 [ Before : with out MVC ]

 

파일 구조

 

위에 보시면 controller, model, routes, views 폴더가 있는 것을 볼 수 있습니다.

 

제일 먼저 저희는 node.js와 express, ejs를 설치해줘야 합니다.

npm init -y
npm install express ejs

 

views 폴더는 [Before], [After]에서 공용으로 사용할 것이기 때문에 미리 만들어 두겠습니다.

< index.ejs >

<!DOCTYPE html>
<html lang="ko"> 
    <!-- en을 ko로 변경 -->
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>홈</h1>
    <a href="/comments">댓글 목록 보기</a>
    <a href="/user">회원 보기</a><br><br>
    <a href="/axios">실습1</a>
</body>
</html>

 

< user.ejs >

<!DOCTYPE html>
<html lang="ko"> 
<!-- ko -->
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>유저 상세페이지</title>
</head>
<body>
    <h1>유저 페이지</h1>
    <a href="/">홈으로 이동하기</a><br><br>
    
    아이디 <input type="text" value="<%= userInfo.realId %>" readonly><br>
    비밀번호 <input type="text" value="<%= userInfo.realPw %>" readonly><br>
    이름 <input type="text" value="<%= userInfo.name %>" readonly><br>
    나이 <input type="text" value="<%= userInfo.age %>" readonly><br>

</body>
</html>

 

< comment >

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>댓글 자세히 보기</title>
</head>
<body>
    <h1><%= commentInfo.userid %>님의 댓글입니다.</h1>
    <a href="/comments">댓글 목록</a>

    <p>작성일 : <%= commentInfo.date %></p>
    <p>댓글 내용 : <%= commentInfo.comment %></p>
</body>
</html>

 

< comments >

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>댓글 목록 보기</title>
</head>
<body>
    <h1>댓글 목록</h1>
    <a href="/">홈으로 이동</a>

    <!-- commentsInfos가 어떻게 오는지 확인 , 배열 형식으로 온다--> 
    <ul>
        <% for(let i = 0; i < commentInfos.length; i++) { %>
            <li>
                <b><%= commentInfos[i].userid %></b>
                <a href="/comment/<%= commentInfos[i].id  %>"><%= commentInfos[i].comment %></a>
            </li>
        <% } %>
    </ul>
</body>
</html>

 


 

< app.js >

const express = require('express');
const app = express();
const PORT = 8000;

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

const comments = [ {
    id:1,
    userid: 'helloword',
    date: '2022-11-31',
    comment: '반가워요'
},{
    id:2,
    userid: 'Hi',
    date: '2023-11-31',
    comment: '안녕'
}, {
    id:3,
    userid: 'bye',
    date: '2021-11-31',
    comment: '잘가'
},{
    id:4,
    userid: 'good',
    date: '2024-11-31',
    comment: '잘했어'
}];


const userInfo = {
    realId: 'helloworld',
    realPw: '1234',
    name: '홍길동',
    age: 20,
}


// [BEFORE] MVC적용 전에는 app.js에서 라우터 정의
// 단점: 라우터(경로)가 많아진다면 app.js 코드가 길어짐 = > 유지보수성 하락

// GET
app.get('/', (req,res) => {
    res.render('index');
})

// GET / comments <= 요청 보냄
// (임시) DB로부터 받아온 데이터 댓글 목록
// 임시로 DB로부터 나온 데이터를 받아주는 모습을 만듬(DB랑 실제로 연결하지 않아서)
app.get('/comments', (req, res)=>{
    console.log(comments) // {},{},{},{}
    console.log('comments',{commentInfos: comments}); //commentInfos 키값, comments value값
    res.render('comments',{commentInfos: comments});
})

// GET /cooment/ :id
app.get('/comment/:id', (req,res) =>{
    // req.query : /comment?id=1
    // ':'뒤에 변수 이름을 붙인다.
    console.log(req.params); // { id : '1' } : 라우트 매개변수에 대한 정보가 담겨있음
    console.log('id >', req.params.id);

    const commentId = req.params.id; // 댓글 아이디, url로 들어온 매개변수

    console.log(comments[commentId - 1]);

    // err처리, 존재하지 않는 댓글 id 접속시 404 페이지
    if(commentId < 1 || commentId > comments.length) {
        return res.render('404');
    }

    console.log(typeof commentId); // string

    // err처리 => :id 변수에 숫자가 아닌 값(commentID가 아닌 값)이 온다면 404페이지
    if(isNaN(commentId)) {
        return res.render(404)
    }
    // 배열의 index번호로 접근
    res.render('comment', { commentInfo: comments[commentId - 1]});
})

app.get('/user', (req, res) =>{
    res.render('user', {userInfo});
})

// [404 error]
// 맨 마지막에 라우트로 선언 : 위에다 하게되면 나머지 코드 무시되기 때문
app.get('*', (req, res) =>{
    res.render('404');
})

app.listen(PORT, () =>{
    console.log(`http://localhost:${PORT}`);
});

 

설명 

  • 우리는 지금 MVC를 설정하기 전에 모습이기 때문에 DB값을 app.js안에 넣어 줬습니다.
  • 우리가 사용할 파일은 app.jsviews폴더에 있는 파일들만 사용할 것입니다.

 

params

  • params는 /comment?id=1, 과 같이  ':' 으로 시작하는 부분을 변수로 지정할 수 있다.

 

app.get('/comment/:id => 

  • 해달 부분의 마지막 res.rend부분을 보면
    • commentInfo: comments[commentId - 1], comments[index] 인덱스 값에 해당하는 배열을 전송

 

app.get('*', (req,res) ) 

  • 위에서 app.get 경로를 지정해준 곳 외에서 들어오는 모든 경로를 에러처리

 

 

 

 

지금까지 MVC를 적용하지 않은 평범한 구조였습니다, 그러면 이제 MVC를 적용한 모습으로 꾸며 보겠습니다!!

MVC 패턴으로 만들 경우, 처음 구조를 짜는 것이 복잡할 수 있지만, 골격을 짠 이후에는 좀더 코드를 관리하기 수월해 집니다.

 

실습 [After : with MVC]

 

우선 각 폴더에 무엇이 들어갈지 생각해 봅시다!

 

Model :

  • DB를 관리하는 장소, 데이터 처리

Views :

  • html/css(ejs)등 클라이언트 화면에 표시되는 부분 

Controller : 

  • View와 Model을 연결
  • GUI화면, 데이터를 읽기, 쓰기, 지우기 등 제어

Routes:

  • Controller와 연결지어 사용한다.
  • 각 js 파일, 사이트 경로에 대해 정의한다.

 

먼저 간결해진 app.js와 추가된 부분에 대해 알아보자

const express = require('express');
const app = express();
const PORT = 8000;

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

//[AFTER] MVC 적용 후 => Router 객채로 라우터 분리
const indexRouter = require('./routes/index'); // index는 생략가능
// 미들웨어 등록
app.use('/', indexRouter); // localhost:PORT/ 경로를 기본으로 ./routes/index.js 파일에 선언한 대로 동작

const userRouter = require('./routes/user');
app.use('/user',userRouter); // localhost:PORT/user 경로를 기본으로 ./routes/user.js 파일에 선언한 대로 동작

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

app.get('*', (req, res) =>{
    res.render('404');
})

app.listen(PORT, () =>{
    console.log(`http://localhost:${PORT}`);
});

 

중간 부분을 보시면

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

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

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

 

해당 routes폴더 경로 폴더에 정의된 js파일에 선언한 대로 동작을 하겠단 의미다.

app.use('/기본값경로', 변수)를 통해 미들웨어를 등록한다. 기본값경로를 설정해 놓으면 해당 파일에서 '/'로 경로를 짧게 사용할 수 있다.

 

* 주의 *

  •  routes폴더 js파일에서 사용되는 '/' 의미는 '/기본값경로' 와 같은 의미고 생략해서 사용하는 것으로 위 코드에서 미리 만든 것

 

< model : Comment.js>

// (임시) DB로부터 받아온 데이터 댓글 목록 (가정)
// 함수 호출시 배열 리턴
exports.commentInfos = () =>{
    return [ {
        id:1,
        userid: 'helloword',
        date: '2022-11-31',
        comment: '반가워요'
    },{
        id:2,
        userid: 'Hi',
        date: '2023-11-31',
        comment: '안녕'
    }, {
        id:3,
        userid: 'bye',
        date: '2021-11-31',
        comment: '잘가'
    },{
        id:4,
        userid: 'good',
        date: '2024-11-31',
        comment: '잘했어'
    }];
}

 

< model : User.js > 

// (임시) DB에서 데이터를 받음
exports.userInfo = () =>{
    return {
        realId: 'helloworld',
        realPw: '1234',
        name: '홍길동',
        age: 20,
    }
}

 

위 model폴더에 있는 파일들에 내용들을 보면,

기존에 app.js에 정의 했었는데 이제는 각 파일을 만들어서 데이터들을 관리한다.

=> 사용할 때는 " exports.이름 "으로 내보내 줘야한다, 현재 MySQL과 연결 되어 있지 않아서 값들을 정의해 놓았다.

 

< controller : Cmain.js >

// [After] Model 연결
const Comment = require('../model/Comment');

exports.main = (req,res) =>{
    res.render('index');
};

// GET /comments
exports.comments = (req, res)=>{
    // console.log(comments) // {},{},{},{}
   	//commentInfos 키값, comments value값
    // res.render('comments',{commentInfos: comments});

    // -- controller---
    console.log(Comment.commentInfos());
    res.render('comments',{commentInfos: Comment.commentInfos()});
};

// GET /comment/:id
exports.comment = (req,res) =>{
    // ========= 3 ==========
    const comments = Comment.commentInfos(); // model 연결 후 추가
    // ======================

    // req.query : /comment?id=1
    // ':'뒤에 변수 이름을 붙인다.
    console.log(req.params); // { id : '1' } : 라우트 매개변수에 대한 정보가 담겨있음
    console.log('id >', req.params.id);

    const commentId = req.params.id; // 댓글 아이디, url로 들어온 매개변수

    console.log(comments[commentId - 1]);

    // err처리, 존재하지 않는 댓글 id 접속시 404 페이지
    if(commentId < 1 || commentId > comments.length) {
        return res.render('404');
    }

    console.log(typeof commentId); // string

    // err처리 => :id 변수에 숫자가 아닌 값(commentID가 아닌 값)이 온다면 404페이지
    if(isNaN(commentId)) {
        return res.render(404)
    }
    // 배열의 index번호로 접근
    res.render('comment', { commentInfo: comments[commentId - 1]});
}

 

먼저 DB를 사용하기 위해서 사용하고자 하는 DB model과 연결 코드를 작성한다.

 

const 변수명 = require('~~/model/DB파일)

DB를 사용할 때는 선언한 " 변수명.DB명() "

 

< controller : Cuser.js >

 

// 유저에 대한 처리
// DB연결
const User = require('../model/User');

// GET /
exports.user = (req, res) =>{
    res.render('user', {userInfo: User.userInfo()});
};

 

user DB를 사용하기 위해 model 연결!

 

이제 routes를 확인해 봅시다!

 

< routes : index.js >

const express = require('express');
const router = express.Router();

// index.js => localhost:PORT/ 

//controller 파일
const controller = require('../controller/Cmain');

// 이 경로로 들어갔을 때 실행될 함수
// controller에다가 함수를 정의

// controller 연결
// 경로를 컨드롤러와 연결지어 사용 가능
router.get('/', controller.main);
router.get('/comments', controller.comments);
router.get('/comment/:id', controller.comment);

// module.exports를 통해서 router를 등록해줘야 다른 모듈에서 사용 가능하다.
module.exports = router;

 

router을 사용하기 위해서 express.Router()를 사용해야 한다.

"module.exportx = router"로 다른 곳에서 router 모듈을 사용할 수 있게 만들어 준다.

 

Controller 파일들을 사용하기 위해서는 Controller 파일과 연결 시켜줘야 한다.

변수명 = require('~~/controller폴더/파일')

 

경로를 컨트롤러와 연결 시키기 위해서는

  • router.get('/경로', controller변수.정의된이름) 형식으로 만든다.
  • 이전에는 app.get()형식을 router.get()형식으로 간결하게 표현한 것
  • * 정의된이름 : controller 파일에 만들어 놓은 exports.main = () => {}
  • ('/') 경로는 app.js에서 만든 경로의 기본값을 축약해서 사용한 것

 

< routes : user.js >

// 라우터 연결
const express = require('express');
const router = express.Router();

// 컨트롤러 파일
const controller = require('../controller/Cuser');

// localhost:PORT/user 가 기본 경로가 된다.

// GET /user
router.get('/', controller.user);

module.exports = router;

 

위에서 설명 했던 것으로 Get /user 경로를 보면

router.get('/', controller.user)로 정의 되어 있다.

  • => ('/') 의미는 app.js에서 설정해놓은 '/user'를 생략해 놓은 경로
    • 실제 경로는 http://localhost:8000/user 가 된다.

 

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

(app.js > user 파트)

 

실습 ( 추가하기 )

이번에는 이전에 해본 아이디와 비밀번호를 입력했을 때 올바른 아이디와 비밀번호가 왔는지 체크 하는 것을 추가해보겠습니다.

 

< app.js 추가 >

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

 

< views : axios 파일 생성 >

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Axios Post</title>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <style>
        div.result {
            font-size: 24px;
            font-weight: 700;
        }
        .success{
            color: blue;
        }
        .error{
            color: red;
        }
    </style>
</head>
<body>
    <form name="prac1">
        <input type="text" id="id" name="id">
        <label for="">아이디</label>
        <input type="password" id="pw" name="pw">
        <label for="">비밀번호</label>
        <button type="button" onclick="login()">로그인</button>
    </form>
    
    <div class="result"></div>

    <script>
        const resultBox = document.querySelector('.result');
        function login(){
            const form = document.forms['prac1'];
            const data = {
                id: form.id.value,
                pw: form.pw.value
            }

            if(!form.id.checkValidity() || !form.pw.checkValidity()){
                resultBox.textContent = `아이디와 패스워드는 필수입니다.`;
                return;
            }

            axios({
                method: 'post',
                url: '/axios',
                data: data
            }).then((res) => {
                console.log(res.data);
                if(res.data.isSuccess){
                    // console.log(responsed.data.userInfo);
                    resultBox.textContent = `${res.data.userInfo.id}님! 로그인 성공`;
                    resultBox.classList.remove('error');
                    resultBox.classList.add('success');
                }else{
                    resultBox.textContent = `아이디 또는 패스워드가 잘못됨`
                    resultBox.classList.remove('success');
                    resultBox.classList.add('error');
                }
            })
        }
    </script>
</body>
</html>

 

 

< route : axios.js 추가 >

const express = require('express');
const router = express.Router();

// 컨트롤러 연결
const controller = require('../controller/Clogin');


router.get('/', controller.axios);
// /axios
router.post('/',controller.axiosP);
// /axios

module.exports = router;

 

만약 router.pos('/axios', controller.axiosP);를 사용하면

=> /axios/axios 가 되기 때문에 조심해야한다.

 

< model : Login.js 추가 >

exports.logins = () =>{
    return [{
        id: 'minsu',
        pw: '1234'
    },
    {
        id: 'yanado',
        pw: '4321'
    }
]
}

 

데이터베이스에 두개의 아이디와 두개의 비밀번호를 주었다.

따라서 우리는 두개의 값이 둘다 적용 되었는지 확인해야 한다.

 

< controller : Clogin.js 추가 >

//DB 연결
const Login = require('../model/Login');

exports.axios = (req, res) => {
    res.render('axios');
}

exports.axiosP = (req, res) => {
    console.log(Login.logins());
    const result = Login.logins();

    if(result[0].id === req.body.id && result[0].pw === req.body.pw){
            res.send({userInfo: result[0], isSuccess: true});
        } else if(result[1].id === req.body.id && result[1].pw === req.body.pw){
            res.send({userInfo: result[1], isSuccess: true});
        } else{
            res.send({isSuccess: false});
        }
}

 

배열로 데이터가 들어가 있기 때문에 배열[index] 값으로 각 아이디와 비밀번호가 맞는지 확인해야 한다.

 

 

 


이상 MVC를 적용한 모습이였습니다.

다음에는 MVC와 MySQL을 연결해서 값을 입력하는 방법을 보겠습니다👋🏻

반응형