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();
}