포스팅 주제
- 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.js와 views폴더에 있는 파일들만 사용할 것입니다.
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을 연결해서 값을 입력하는 방법을 보겠습니다👋🏻
'웹 개발' 카테고리의 다른 글
[포스코X코딩온] MVC - 로그인 만들기 (1) | 2023.12.04 |
---|---|
[포스코X코딩온] 모델 뷰 컨트롤 MVC - MySQL 연결 (0) | 2023.12.01 |
[포스코X코딩온] 데이터베이스 SQL문 (1) | 2023.11.29 |
[포스코X코딩온] 데이터베이스 (MySQL) 설치 (0) | 2023.11.29 |
[포스코X코딩온] 파일업로드 Multer (0) | 2023.11.28 |