[Node JS] MVC 패턴 / 라우터 쪼개기

Onam Kwon·2022년 8월 28일
3

Node JS

목록 보기
10/25

MVC pattern

  • Model View Controller 세가지로 분리된 형태의 아키텍쳐.
    • Model: 데이터 및 로직처리
    • View: 사용자 인터페이스 처리
    • Controller: 모델과 뷰의 중개 역할, 요청을 해당 모델로 안내 및 작업 완료 후 클라이언트로 응답
  • 전처럼 라우터에 모든 코드를 작성하고 결합된 형태에서는 어플리케이션의 확장, 유지 및 보수가 어렵지만 MVC패턴을 적용하면 이런부분이 개선된다. 추가로 가독성도 좋아짐.
tree -I 'node_modules|Dockerfile|package*.json'
  • 위의 CLI 명령어를 통해 디렉토리 구조 확인 (구조 확인에 있어서 필요 없는 파일은 제외).

▼ 디렉토리 구조 ▼

.
├── README.md
├── controllers
│   ├── 2048
│   │   └── 2048.controller.js
│   ├── authMiddleware.js
│   ├── board
│   │   └── board.controller.js
│   ├── chat
│   │   ├── chat.controller.js
│   │   └── connectSocket.js
│   ├── encryptPassword.js
│   ├── home
│   │   └── home.controller.js
│   ├── issueToken.js
│   ├── loginCheck.js
│   ├── user
│   │   └── user.controller.js
│   └── userValidateCheck.js
├── models
│   ├── boardDBController.js
│   ├── comparePassword.js
│   ├── connectMongoDB.js
│   ├── connectMySQL.js
│   ├── findUser.js
│   └── user.js
├── public
│   └── css
│       └── styles.css
├── pythonScript
│   ├── RANK.db
│   ├── dbCompare.py
│   ├── dbDisplay.py
│   └── dbPost.py
├── routes
│   ├── 2048Router.js
│   ├── boardRouter.js
│   ├── chatRouter.js
│   ├── homeRouter.js
│   ├── tetrisRouter.js
│   └── userRouter.js
├── server.js
└── views
    ├── 2048
    │   ├── 2048.css
    │   ├── 2048.ejs
    │   └── 2048.js
    ├── Snake
    │   ├── snake.css
    │   ├── snake.html
    │   └── snake.js
    ├── Tetris
    │   ├── tetris.css
    │   ├── tetris.html
    │   └── tetris.js
    ├── board
    │   ├── article.ejs
    │   ├── board.ejs
    │   ├── boardWrite.ejs
    │   ├── editArticle.ejs
    │   └── login.html
    ├── chat
    │   ├── chat.ejs
    │   └── chat.html
    ├── home
    │   ├── home.ejs
    │   └── home.html
    └── user
        ├── user.ejs
        └── user.html

19 directories, 50 files
  • 전에는 server.js파일이 모든 라우터와 함수를 포함하고 있었다. 라우터도 10개가 넘기 때문에 프로젝트가 점점 더 복잡해질 수록 코드의 길이와 라우터의 갯수도 증가한다.
    • MVC패턴을 적용해 라우터를 쪼개서 관리할 수 있다.
  • models views routes controllers 네가지 디렉토리를 만들었다.
    • models: DB에 접근하는 및 로직에 관련된 코드.
      • connectDB
    • views: 클라이언트측 관련 파일을 모아둔다.
      • html ejs
    • controllers: 라우터 관련 파일을 모아두며 클라이언트 요청으로부터 대응하는 모델에 안내하며 작업이 끝난 후 다시 응답하는 역할. 뷰와 모델의 중간 역할을 함.
      • home.controller.js user.controller.js board.controller.js
      • homeRouter.js userRouter.js boardRouter.js

라우터 쪼개기

  • 현재 작업중인 디렉토리를 기준으로 블로그를 작성중이라 homeRouter를 예시로 들면서 작성하겠습니다.

server.js

// server.js
const express = require('express');
const app = express();

// allows you to ejs view engine.
app.set('view engine', 'ejs');

// Routers.
const homeRouter = require('./routes/homeRouter');

const port = 80;
const server = app.listen(port, function() {
    console.log('Listening on '+port);
});

app.use('/', homeRouter);
  • 더이상 app.get('/', (req, res) => {return res.render('/home', {user: user});}); 이런식으로 하지 않고, 현재 디렉토리에서 아까 만들어둔 routes디렉토리의 앞으로 만들 homeRouter.js파일을 포함해준다.
  • homeRouter.js는 앞으로 '/'경로로 오는 라우터를 관리할것이다.

homeRouter.js

// homeRouter.js
const express = require("express");
const router = express.Router();

const auth = require("../controllers/authMiddleware");
const homeMiddleWare = require('../controllers/home/home.controller');

router.use('/', auth);

// Home page.
router.get('/', homeMiddleWare.showHome);

module.exports = router;
  • home과 관련된 모든 라우터들을 homeRouter.js로 가져온다.
  • 하지만 여기서도 app.get('/', (req, res) => {return res.render('/home', {user: user});}); 이런식으로 하지 않고 또 한번 나눠준다.
  • router.get('/', homeMiddleWare.showHome); 처럼 router를 이용해 경로만 나눠준 후 controllers/home경로에 있는 home.controller.js파일의 함수를 호출해 준다.
    • 함수 이름은 마음대로 작성하면 됩니다.
  • 위처럼 경로별로 라우팅해 해당 라우터에서 미들웨어 함수만 불러준다.

미들웨어 쪼개기

home.controller.js

// home.controller.js

const path = require('path');

exports.showHome = (req, res) => {
    const user = req.decoded;
    if(user) {
        return res.render(path.join(__dirname, '../../views/home/home'), {user:user});
    } else {
        return res.sendFile(path.join(__dirname, '../../views/home/home.html'));
    }
}
  • home.controller.js 파일에서 방금 위에서 호출한 함수의 이름대로 미들웨어 함수를 만들어 예시처럼 정의해 주면 된다.
  • path.join() 함수는 받은 인자들을 합쳐 경로값을 리턴해준다.
  • __dirname은 현재 파일 즉, home.controller.js파일이 위치하고 있는 경로를 나타낸다.

예시

  • 위와 같은 방법으로 다른 파일들을 적용했을때 한가지 예시.

server.js

// server.js

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

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

// importing body-parser to create bodyParser object
const bodyParser = require('body-parser');
// allows you to use req.body var when you use http post method.
app.use(bodyParser.urlencoded({ extended: true }));

// Cookies.
const cookieParser = require('cookie-parser');
app.use(cookieParser());

// allows you to ejs view engine.
app.set('view engine', 'ejs');

// Socket.
const connectSocket = require('./controllers/chat/connectSocket');
// MongoDB.
const { connectMongoDB } = require('./models/connectMongoDB');

// Routers.
const homeRouter = require('./routes/homeRouter');
const game2048Router = require('./routes/2048Router');
const gameTetrisRouter = require('./routes/tetrisRouter');
const userRouter = require('./routes/userRouter');
const chatRouter = require('./routes/chatRouter');
const boardRouter = require('./routes/boardRouter');

const port = 80;
const server = app.listen(port, function() {
    console.log('Listening on '+port);
});

connectSocket(server);
connectMongoDB();

app.use('/', homeRouter);
app.use('/2048', game2048Router);
app.use('/tetris', gameTetrisRouter);
app.use('/user', userRouter);
app.use('/chat', chatRouter);
app.use('/board', boardRouter);

userRouter.js

// loginRouter.js

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

// Importing controller
const userMiddleWare = require('../controllers/user/user.controller');

const auth = require("../controllers/authMiddleware");
router.use('/', auth);

router.get('/', userMiddleWare.showMain);
router.post('/:id/:address/:pw/:pwc', userMiddleWare.signUp);
router.post('/:id/:pw', userMiddleWare.signIn);
router.delete('/logout', userMiddleWare.signOut);

module.exports = router;

user.controller.js

// login.controller.js

const express = require("express");
const app = express();

const path = require('path');

// allows you to ejs view engine.
app.set('view engine', 'ejs');

// importing user schema.
const User = require('../../models/user');

const userValidateCheck = require("../../controllers/userValidateCheck");
const encryptPassword = require("../../controllers/encryptPassword");
const loginCheck = require("../../controllers/loginCheck");
const issueToken = require("../../controllers/issueToken");

// Main login page.
 exports.showMain = (req, res) => {
    const user = req.decoded;
    if(user) {
        return res.render(path.join(__dirname, '../../views/user/user'), {user:user});
    } else {
        return res.sendFile(path.join(__dirname, '../../views/user/user.html'));
    }
}

// Sign up.
exports.signUp = async (req, res) => {
    const { id, address, pw, pwc } = req.body;
    const errorFlag = await userValidateCheck(id, address, pw, pwc);
    if(errorFlag) { // user typed something wrong.
        return res.status(200).send(errorFlag);
    }
    const user = new User(req.body);
    user.pw = await encryptPassword(user.pw);
    user.save();
    return res.status(200).send('Your account has been created successfully, you can now log in.');
}

// Sing in.
exports.signIn = async (req, res) => {
    const { id, pw } = req.body;
    const userConfirmed = await loginCheck(id, pw);
    if(userConfirmed) {
        const token = await issueToken(id);
        return res
            .cookie('user', token,{maxAge: 30 * 60 * 1000}) // 1000 is a sec
            .end();
    } else if(userConfirmed==false) {
        return res.status(200).send('Your ID is not correct.');
    }
    else {
        return res.status(200).send('Your password is not correct.');
    }
}

// Sign out.
exports.signOut = (req, res) => {
    return res.clearCookie('user').end();
}
profile
권오남 / Onam Kwon

0개의 댓글