Node.js와 MVC 패턴으로 간단한 웹 구현



1. MVC 패턴이란?

MVC는 Model-View-Controller의 약자로, 애플리케이션을 세 부분으로 나누어 개발하는 소프트웨어 디자인 패턴입니다.

  • Model: 데이터와 비즈니스 로직을 처리합니다.
  • View: 사용자에게 데이터를 표시하는 부분입니다.
  • Controller: 사용자 입력을 받아서 처리하고, 모델과 뷰를 연결합니다.


2. 프로젝트 구조

프로젝트를 MVC 패턴으로 구성하기 위해 다음과 같은 디렉토리 구조를 사용합니다.

15-mvc/
├── controller/
│   ├── Cmain.js
│   └── Cuser.js
├── model/
│   ├── Comment.js
│   └── User.js
├── routes/
│   ├── index.js
│   └── user.js
├── views/
│   ├── 404.ejs
│   ├── comment.ejs
│   ├── comments.ejs
│   ├── index.ejs
│   └── user.ejs
├── app.js
├── package.json
└── package-lock.json

이 구조를 통해 각 부분을 분리하여 관리할 수 있습니다.


3. 기본 모듈 설치 및 설정

프로젝트를 시작하기 전에 필요한 모듈을 설치하고 설정합니다.

{
  "name": "15-mvc",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "ejs": "^3.1.10",
    "express": "^4.19.2"
  }
}


4. Express 애플리케이션 설정

app.js 파일에서 Express 애플리케이션을 설정합니다.

const express = require('express'); // Express 모듈을 가져옴
const app = express(); // 애플리케이션 객체를 생성함
const PORT = 8000; // 서버가 실행될 포트를 8000번으로 설정함

app.set('view engine', 'ejs'); // EJS 템플릿 엔진을 사용하도록 설정함
app.set('views', './views'); // 뷰 파일들이 위치한 디렉토리를 설정함

const indexRouter = require('./routes/index'); // 메인 라우터를 불러옴
const userRouter = require('./routes/user'); // 유저 라우터를 불러옴

app.use('/', indexRouter); // 메인 라우터를 '/' 경로에 연결함
app.use('/user', userRouter); // 유저 라우터를 '/user' 경로에 연결함

// 모든 정의되지 않은 경로에 대해 404 페이지를 렌더링함 (후순위에 두어야 기존 라우터를 성공적으로 불러옴)
app.get('*', (req, res) => {
    res.render('404');
});

// 서버를 지정된 포트에서 실행함
app.listen(PORT, () => {
    console.log(`${PORT} 서버 연결 성공`);
});


5. 모델 설정

모델은 데이터와 관련된 로직을 처리합니다. 여기서는 임시 데이터베이스로 댓글과 유저 정보를 가져오는 함수를 정의합니다.

model/Comment.js

// getDbComments 함수는 임시로 DB에서 전체 댓글 목록을 읽어오는 역할을 함
exports.getDbComments = () => {
    return [
        // 하드코딩된 댓글 데이터 배열
        { id: 1, userid: 'helloworld', date: '2022-10-31', comment: '안녕하세요^~^' },
        { id: 2, userid: 'happy', date: '2022-11-01', comment: '반가워유' },
        { id: 3, userid: 'lucky', date: '2022-11-02', comment: '오 신기하군' },
        { id: 4, userid: 'bestpart', date: '2022-11-02', comment: '첫 댓글입니당ㅎㅎ' },
    ];
};

model/User.js

// getDbUser 함수는 임시로 DB에서 유저 한 명의 정보를 읽어오는 역할을 함
exports.getDbUser = () => {
    return {
        realId: 'helloworld',
        realPw: 'qwer1234*',
        name: '홍길동',
        age: 20
    };
};


6. 컨트롤러 설정

컨트롤러는 사용자의 요청을 처리하고, 모델에서 데이터를 가져와 뷰에 전달합니다.

controller/Cmain.js

// 비구조화 할당
// const commentModule = require('../model/Comment');
// const getDbComments = commentModule.getDbComments;
const { getDbComments } = require('../model/Comment'); // 댓글 데이터를 가져오는 함수를 불러옴

// getMain 함수는 메인 페이지를 렌더링함
exports.getMain = (req, res) => {
    res.render('index'); // index.ejs 파일을 렌더링함
};

// getComments 함수는 전체 댓글 목록을 렌더링함
exports.getComments = (req, res) => {
    const comments = getDbComments(); // 댓글 데이터를 가져옴
    res.render('comments', { comments }); // comments.ejs 파일을 렌더링하고, 댓글 데이터를 전달함
};

// getComment 함수는 특정 id의 댓글을 렌더링함
exports.getComment = (req, res) => {
    const commentId = req.params.id; // 요청 URL에서 id를 가져옴
    const dbComments = getDbComments(); // 댓글 데이터를 가져옴

    if (!dbComments[commentId - 1]) {
        return res.render('404'); // id에 해당하는 댓글이 없으면 404 페이지를 렌더링함
    }

    const comment = dbComments[commentId - 1]; // id에 해당하는 댓글을 가져옴
    res.render('comment', { comment }); // comment.ejs 파일을 렌더링하고, 해당 댓글 데이터를 전달함
};


controller/Cuser.js

const { getDbUser } = require('../model/User'); // 유저 데이터를 가져오는 함수를 불러옴

// getUser 함수는 유저 정보를 렌더링함
exports.getUser = (req, res) => {
    const user = getDbUser(); // 유저 데이터를 가져옴
    res.render('user', { user }); // user.ejs 파일을 렌더링하고, 유저 데이터를 전달함
};


7. 라우터 설정

라우터는 URL 요청을 컨트롤러의 특정 함수와 연결합니다.

routes/index.js

const express = require('express'); // Express 모듈을 가져옴
const controller = require('../controller/Cmain'); // 메인 컨트롤러를 가져옴
const router = express.Router(); // 라우터 객체를 생성함

// 메인 페이지 요청을 처리함
router.get('/', controller.getMain);

// 댓글 목록 페이지 요청을 처리함
router.get('/comments', controller.getComments);

// 특정 댓글 페이지 요청을 처리함, :id는 변수를 의미함
router.get('/comment/:id', controller.getComment);

module.exports = router; // 라우터 객체를 모듈로 내보냄

routes/user.js

const express = require('express'); // Express 모듈을 가져옴
const controller = require('../controller/Cuser'); // 유저 컨트롤러를 가져옴
const router = express.Router(); // 라우터 객체를 생성함

// 유저 정보 페이지 요청을 처리함
router.get('/', controller.getUser);

module.exports = router; // 라우터 객체를 모듈로 내보냄


8. 뷰 설정

뷰 파일은 사용자에게 보여지는 화면을 구성합니다. EJS 템플릿을 사용하여 데이터를 동적으로 렌더링합니다.

views/index.ejs

<!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>MVC 패턴을 배워보자</h1>
    <a href="/comments">댓글 목록 보기</a>
    <a href="/user">유저 정보 보기</a>
</body>
</html>


views/comments.ejs

<!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>댓글 목록</h1>
    <a href="/">홈으로 이동하기</a>

    <ul>
        <!-- 서버에서 전달된 `comments` 배열을 사용하여 각 댓글을 목록으로 렌더링 -->
        <% for (let i = 0; i < comments.length; i++) { %>
            <li>
                <%= comments[i].userid %> -
                <a href="/comment/<%= i + 1 %>"><%= comments[i].comment %></a>
            </li>
        <% } %>
    </ul>
</body>
</html>


views/comment.ejs

<!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>
    <!-- 서버에서 전달된 `comment` 객체를 사용하여 댓글의 세부 정보를 렌더링 -->
    <h1>댓글 자세히 보기</h1>
    <a href="/comments">목록 보기</a>
    <div><%= comment.userid %> 님의 댓글입니다.</div>
    <div>작성일: <%= comment.date %></div>
    <div>내용: <%= comment.comment %></div>
</body>
</html>


views/user.ejs

<!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>유저 조회</h1>
    <a href="/">홈으로 이동</a>
    <br>
    <!-- 서버에서 전달된 `user` 객체를 사용하여 유저의 세부 정보를 렌더링 -->
    <input type="text" value="<%= user.realId %>" readonly><br>
    <input type="text" value="<%= user.realPw %>" readonly><br>
    <input type="text" value="<%= user.name %>" readonly><br>
    <input type="number" value="<%= user.age %>" readonly><br>
</body>
</html>

views/404.ejs

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404 페이지</title>
</head>
<body>
    <h1>404. 페이지를 찾을 수 없습니다.</h1>
    <p>죄송합니다. 찾을 수 없는 페이지입니다.</p>
    <a href="/">홈으로 가기</a>
</body>
</html>