웹 애플리케이션 개발 시, 코드 구조를 깔끔하게 구성하고 모듈화하는 것은 반드시 필요한 과정입니다.
Express.js를 사용하여 웹 애플리케이션 개발하는 경우 이를 구현하기 위해 많은 좋은 방법이 있습니다.
이 글에서는 Express에서 모델-뷰-컨트롤러(MVC) 패턴을 적용하여 API 서버를 구축하는 방법에 대해 소개하고자 합니다.
초보 개발자분들은 모듈화와 디자인 패턴에 대한 필요성을 잘 느끼지 못하실 수 있습니다.
그러나 작은 프로젝트라도 실제 서비스하는 코드를 직접 작성 해보신다면,
며칠만 지나도 해당 코드를 유지 보수 하는 데에 엄청난 어려움을 느끼게 됩니다.
MVC 패턴이란 Model-View-Controller(MVC) 패턴은 개발할 애플리케이션을 세 가지 역할(컴포넌트)로 구분하는 소프트웨어 아키텍처 디자인 패턴입니다.
Model: 데이터베이스와 통신하며 서버에서 사용되는 데이터 구조를 정의합니다.
View: 사용자 인터페이스를 생성하고 보여주는 코드를 정의합니다.(html,ejs등)
이 글에서는 API 서버를 구축하므로 View는 생략합니다.
Controller: 사용자의 요청을 처리하고 알맞은 데이터를 보여주기 위해 Model 과 View 사이에서 데이터를 이동하며 로직을 처리하는 역할을 합니다.
무슨 말인지 저도 모르겠습니다. 아래 예제를 보며 알아봅시다
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const app = express();
// DB , 스키마 설정
const MemoSchema = new mongoose.Schema({
content: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
});
const Memo = mongoose.model('Memo', MemoSchema);
// 미들웨어 설정
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// 데이터베이스 연결
mongoose.connect('mongodb://localhost/memo-app', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// 메모 작성
app.post('/memo', async (req, res) => {
try {
const memo = await Memo.create(req.body);
res.status(201).json(memo);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// 모든 메모 조회
app.get('/memo', async (req, res) => {
try {
const memos = await Memo.find().sort({ createdAt: -1 });
res.status(200).json(memos);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
//메모 조회
app.get('/memo/:id', async (req, res) => {
try {
const memo = await Memo.findById(req.params.id);
if (!memo) {
return res.status(404).json({ error: 'Memo not found' });
}
res.status(200).json(memo);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
//메모 수정
app.put('/memo/:id', async (req, res) => {
try {
const memo = await Memo.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!memo) {
return res.status(404).json({ error: 'Memo not found' });
}
res.status(200).json(memo);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
//메모 삭제
app.delete('/memo/:id', async (req, res) => {
try {
const memo = await Memo.findByIdAndRemove(req.params.id);
if (!memo) {
return res.status(404).json({ error: 'Memo not found' });
}
res.status(200).json({ message: 'Memo deleted successfully' });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// 서버 시작
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
여러분과 제가 자주 작성하는 app.js에 전부 때려넣은 코드 입니다.
로직은 아래와 같습니다.
DB관련 로직과 스키마 정의한 로직을 Model로 나누고,
응답시 전송할 ejs,html 파일들을 View 로 나누고,
요청에 따른 응답 로직을 Controllers로 나누어 관리하는 디자인 패턴이 바로 MVC 패턴입니다.
(위 코드에서는 View 파일이 없는 API서버이기 때문에 View 모듈은 생략합니다.)
이제 app.js를 위 방식으로 하나 하나 나누어 보겠습니다.
app.js는 놔두고 controllers,models,routes 폴더를 만들어 줍니다.
.
├── app.js
├── package.json
├── controllers
│ ├── user.controller.js
│ └── ...
├── models
│ ├── user.model.js
│ └── ...
└── routes
├── user.route.js
└── ...
const mongoose = require('mongoose');
const memoSchema = new mongoose.Schema({
content: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Memo', memoSchema);
const Memo = require('../models/Memo');
exports.createMemo = async (req, res) => {
try {
const memo = await Memo.create(req.body);
res.status(201).json(memo);
} catch (err) {
res.status(400).json({ error: err.message });
}
};
exports.getMemoList = async (req, res) => {
try {
const memos = await Memo.find().sort({ createdAt: -1 });
res.status(200).json(memos);
} catch (err) {
res.status(400).json({ error: err.message });
}
};
exports.getMemo = async (req, res) => {
try {
const memo = await Memo.findById(req.params.id);
if (!memo) {
return res.status(404).json({ error: 'Memo not found' });
}
res.status(200).json(memo);
} catch (err) {
res.status(400).json({ error: err.message });
}
};
exports.updateMemo = async (req, res) => {
try {
const memo = await Memo.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!memo) {
return res.status(404).json({ error: 'Memo not found' });
}
res.status(200).json(memo);
} catch (err) {
res.status(400).json({ error: err.message });
}
};
exports.deleteMemo = async (req, res) => {
try {
const memo = await Memo.findByIdAndRemove(req.params.id);
if (!memo) {
return res.status(404).json({ error: 'Memo not found' });
}
res.status(200).json({ message: 'Memo deleted successfully' });
} catch (err) {
res.status(400).json({ error: err.message });
}
};
const express = require('express');
const memoController = require('../controllers/memoController');
const router = express.Router();
router.post('/', memoController.createMemo);
router.get('/', memoController.getMemoList);
router.get('/:id', memoController.getMemo);
router.put('/:id', memoController.updateMemo);
router.delete('/:id', memoController.deleteMemo);
module.exports = router;
마지막으로 app.js에서는 미들웨어들을 넣어주고 요청은 라우트로 넘겨줍니다.
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const memoRoutes = require('./routes/memo');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
mongoose.connect('mongodb://localhost/memo-app', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
app.use('/memo', memoRoutes);
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
디자인 패턴을 처음 접하는 분들은 개념이 머릿속에 잘 정리되지 않을 수 있습니다.
그럴때는 위 예시처럼 간단한 서버코드를 한곳에 작성 하신 후
블로그 글의 카테고리를 나눈다는 느낌으로 하나씩 분리를 해보세요.
또한, MVC패턴을 포함한 다른 모든 디자인 패턴들은 가이드라인 일 뿐 정답이 아닙니다.
정해진 규칙을 강박적으로 맞추기보다, 본인의 코드에 맞춘 규칙을 만드는 과정에서 유명한 디자인 패턴으로부터 아이디어와 도움을 얻어 가며 학습한다면, 좋은 디자인 패턴을 가진 코드를 작성 할 수 있을것이라 생각합니다.
글 잘 봤습니다, 많은 도움이 되었습니다.