folder_open

30 天打造 MERN Stack Boilerplate

arrow_right
article

Day 07 - Backend - MVC

Day 07 - Backend - MVC

前面了解了 Backend 所需的 API Server 和 Database,加上 Middleware 這個利器,接著就可以談論怎麼 Express 程式,讓 Client、Express API Server、Database 三者互相溝通協調。

MVC

#

MVC 是一種軟體架構模式,把軟體切割成三大元素:M odel、V iew、C ontroller。如果從 Express Web App 的角度來看,Server 收到 Request 後先比對路由,之後進入 Middlewares 及 Controller 所控制的執行流程,Middlewares 或 Controller 可能隨時操作 Mongoose 的 Model 來向資料庫存取資料,存取結束後進入下一個 Middleware,當所有 Middleware 都走過了,Controller 再把資料灌進 View 樣板中渲染(Render)出 Html。我們在 Boilerplate 中的 View 的部分是使用 React,但那又是另一門學問了,我們留待後面章節再來介紹。

組織模組

#

有了上述 MVC 概念後,我們來看看 Boilerplate 是怎麼拆解和組織架構的:

檔案結構

#

各位可以比對 Day 02 - File Organization 中列出的完整架構,這裡只取出 server 資料夾的部分。

/src/server/
models/
controllers/
middlewares/
routes/
app.js

是程式進入點, 資料夾儲存 Model 相關的模組, 資料夾儲存 Controller 相關的模組,其餘資料夾依此類推,各資料夾內可以建立各自的 來整合資料夾內的模組。

程式碼範例

#

程式進入點依序註冊 Middlewares 與 Routes,接著啟動 Server:

// src/server/app.js
import express from 'express';
import middlewares from './middlewares';
import routes from './routes';
let app = express();
middlewares({ app });
routes({ app });
app.listen(port, (err) => {
console.log('Listening at port', port);
});

統整 Middlewares:

// src/server/middlewares/index.js
import middleware1 from './middleware1';
import middleware2 from './middleware2';
export default ({ app }) => {
app.use(middleware1);
app.use(middleware2);
// ...
};

每一個 Express Middleware 模組獨立寫成一份檔案:

// src/server/middlewares/middleware1.js
// src/server/middlewares/middleware2.js
// ...
export default (req, res, next) => {
if (/* some condition */) {
// send reject response
} else {
next();
}
};

一樣整合整個 App 的路由:

// src/server/routes/index.js
import apiRoutes from './api';
import anotherRoutes from './another';
import stillAnotherRoutes from './stillAnother';
import otherRoutes from './other';
export default ({ app }) => {
apiRoutes({ app });
anotherRoutes({ app });
stillAnotherRoutes({ app });
otherRoutes({ app });
// ...
};

在 API 的路由中設定對應的 Middlewares 和 Controller:

// src/server/routes/api.js
import bodyParser from '../middlewares/bodyParser';
import todoController from '../controllers/todo';
export default ({ app }) => {
app.get('/api/todos', todoController.list);
app.post('/api/todos', bodyParser.json, todoController.create);
app.put('/api/todos/:id', bodyParser.json, todoController.update);
app.delete('/api/todos/:id', todoController.remove);
};

Controller 和 Mongoose Model 互動後取回資料,再以 Json 形式回應給 Client。我們的 Server 主要是用於提供 API 服務,所以此例中沒有給出 Render View 的範例寫法。

import Todo from '../models/Todo';
export default {
list(req, res) {
// ...
},
create(req, res) {
// ...
},
update(req, res) {
// ...
},
remove(req, res) {
Todo.remove({_id: req.params.id}, (err) => {
res.json({});
});
},
};

Model 部分的程式碼在此就不贅述,請參考 Day 05 - Backend - Technique Stack

值得一提的是,我們在這程式中用到了 ES6 的 Object Literal 技巧來傳遞 這個變數,這麼做的好處是保留彈性,日後我們想傳遞別的參數時,不必影響原有的程式架構。

至此,我們算是優雅地建立出 Backend 的服務了,Model、Controller、Middleware、Route、...皆有各自獨立的資料夾,如有必要,也可以建立資料夾內的 index.js,這樣日後要擴充模組時就十分容易,同時也維持了 MVC 的結構。