既然我們的個人博客項目使用的是Express
框架鸥滨,那我們在項目初始化之前鸥咖,先從【初始化一個Express項目】說起,分析下默認的Express腳手架招狸。
本文篇幅稍稍有點長敬拓,超出了計劃,請耐心閱讀【手動捂臉】裙戏。
初始化一個Express項目
在Express
框架的官方文檔中乘凸,是通過生成器工具express-generator
快速生成了一個Express應(yīng)用,相關(guān)步驟如下:
安裝Express生成器
npm install -g express-generator
初始化一個Express應(yīng)用
express express-app
如此便生成了一個名為express-app
的Express應(yīng)用累榜。
目錄結(jié)構(gòu)概覽
首先來看一下這個Express應(yīng)用的目錄結(jié)構(gòu)营勤,目錄結(jié)構(gòu)如下:
express-app
│ app.js
│ package.json
│
├─bin
│ www
│
├─public
│ ├─images
│ ├─javascripts
│ └─stylesheets
│ style.css
│
├─routes
│ index.js
│ users.js
│
└─views
error.jade
index.jade
layout.jade
目錄結(jié)構(gòu)簡介
-
app.js
應(yīng)用的初始化文件,包括引入應(yīng)用程序的基礎(chǔ)依賴項壹罚、設(shè)置視圖即view的引擎目錄以及模板葛作、設(shè)置靜態(tài)資源路徑、配置通用的中間件猖凛、引入路由和一些錯誤處理中間件等赂蠢。 -
package.json
應(yīng)用的配置文件,文件內(nèi)包含程序的基礎(chǔ)信息辨泳、啟動腳本和依賴包等虱岂。 -
bin/www
應(yīng)用的啟動文件玖院,文件內(nèi)包含引用要啟動的應(yīng)用、設(shè)置應(yīng)用監(jiān)聽的端口和啟動http服務(wù)等第岖。 -
public/**
應(yīng)用的靜態(tài)資源文件目錄司恳,該目錄下的文件資源不需要經(jīng)過文件映射就可以直接訪問。 -
routes/**
應(yīng)用的路由文件绍傲,這些路由文件中設(shè)置的接口最終會以指定的HTTP請求方式暴露給用戶,并在用戶請求之后將結(jié)果返回耍共。 -
views
應(yīng)用的視圖文件烫饼,在app.js
中設(shè)置好視圖引擎和模板之后,該目錄即為應(yīng)用視圖的根目錄试读,然后路由文件就會根據(jù)app.js
中的設(shè)置加載并渲染該目錄下的視圖文件杠纵。
應(yīng)用啟動
首先,先要安裝應(yīng)用的依賴包钩骇。
npm install
然后比藻,啟動程序。
node bin/www
查看bin/wwww
文件倘屹,拷打應(yīng)用默認監(jiān)聽的端口是3000银亲,然后訪問localhost:3000
,看到如下界面:
看到上面的頁面,說明我們的應(yīng)用啟動成功纽匙,并且成功地訪問到了視圖引擎目錄view
中的頁面务蝠。
文件詳解
app.js文件詳解
以下是初始化的Express應(yīng)用中的已經(jīng)做了一些注釋說明后的app.js
文件中的代碼:
// 引入依賴包
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
// 引入路由文件
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
// 創(chuàng)建應(yīng)用實例
var app = express();
// 設(shè)置視圖目錄和模板引擎
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// 以下皆為注冊中間件
// 內(nèi)置中間件
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 路由中間件
app.use('/', indexRouter);
app.use('/users', usersRouter);
// 404錯誤處理中間件
app.use(function(req, res, next) {
next(createError(404));
});
// 錯誤處理中間件
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
// 導(dǎo)出app實例對象
module.exports = app;
根據(jù)代碼中的注釋,我們可以看到烛缔,app.js
的作用主要是初始化Express應(yīng)用的一些設(shè)置馏段,包括引入依賴包、引入路由文件践瓷、注冊各類中間件的一些操作院喜。
www文件詳解
以下是添加了注釋后的應(yīng)用啟動文件bin/www
中的代碼:
#!/usr/bin/env node
// 引入依賴
var app = require('../app');
var debug = require('debug')('express:server');
var http = require('http');
// 設(shè)置應(yīng)用監(jiān)聽的端口
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
// 創(chuàng)建http服務(wù)器
var server = http.createServer(app);
// 監(jiān)聽端口
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
// 格式化應(yīng)用監(jiān)聽的端口
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
// http的錯誤監(jiān)聽函數(shù)
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
// http端口監(jiān)聽函數(shù)
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
bin/wwww
文件的內(nèi)容就很簡單了,就是創(chuàng)建HTTP服務(wù)晕翠,監(jiān)聽端口和錯誤喷舀。
router/**詳解
router/**
包含了路由文件,每個路由文件中包含了不同HTTP請求對用的路由以及邏輯處理函數(shù)崖面,此處以router/index.js
文件為例元咙,以下是index.js
文件的代碼:
// 引入依賴包
var express = require('express');
// 創(chuàng)建路由對象
var router = express.Router();
// 獲取首頁
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
路由文件主要相應(yīng)相應(yīng)的HTTP請求,然后執(zhí)行函數(shù)進行相應(yīng)的邏輯處理巫员,以上代碼庶香,是對GET
請求的響應(yīng),然后渲染view
目錄下的index.jade
頁面(在app.js
中設(shè)置了視圖目錄和模板引擎)简识,同時傳遞數(shù)據(jù){title: 'Express'}
給index.jade
頁面赶掖。
view/**詳解
view/**
主要存放應(yīng)用的頁面感猛,以view/index.jade
為例,代碼如下:
extends layout
block content
h1= title
p Welcome to #{title}
頁面文件主要是接收路由文件傳遞的數(shù)據(jù)奢赂,然后生成渲染后的頁面給客戶端陪白,在上一步,傳遞了數(shù)據(jù){title: 'Express'}
給index.jade
頁面膳灶,所以真實的頁面應(yīng)該是如下的代碼:
extends layout
block content
h1= 'Express'
p Welcome to Express
然后客戶端就看到了如下的頁面:
改進
缺點分析
在實際開發(fā)中咱士,我們項目的路由文件的數(shù)目會因項目復(fù)雜度而不同,當(dāng)項目足夠復(fù)雜時轧钓,我們需要引入的路由文件和需要注冊的路由就會更多序厉,同時除了應(yīng)用本身的404和錯誤處理中間件之外,還可能根據(jù)實際需求新增我們自定義的中間件毕箍,如果這些初始化的代碼都放置于app.js
中弛房,無疑,app.js
文件會越來越大越來越臃腫而柑,這并不是一個很好的選擇文捶。同時路由和路由對應(yīng)的處理函數(shù)在一個文件中,這樣的處理不夠優(yōu)雅媒咳,不便于維護粹排,所以進行路由分離顯得很有必要性。當(dāng)然伟葫,這個應(yīng)用只是生成器工具初始化的一個簡單腳手架恨搓,并不是最終的樣子。
MVC框架簡介
MVC
框架是一個經(jīng)典的架構(gòu)模式筏养,其中MVC
分別指的是:
- M: Model(模型斧抱,包含數(shù)據(jù)和數(shù)據(jù)處理方法)
- V: View(視圖,提供視圖渐溶,即頁面)
- Controller: (控制層辉浦,負責(zé)邏輯處理)
對應(yīng)的,在Node中的MVC
框架茎辐,處理流程應(yīng)該是以下步驟:
- 服務(wù)端收到客戶端的請求
- 路由層開始處理服務(wù)端接收到的請求宪郊,匹配相應(yīng)的路由
- 匹配到相應(yīng)的路由之后調(diào)用對應(yīng)的
controller
(即對應(yīng)的邏輯處理函數(shù)) -
controller
接收到請求之后,向model
層取用數(shù)據(jù) -
model
層接收到controller
層取用數(shù)據(jù)的請求之后拖陆,將數(shù)據(jù)返回給controller
層 -
controller
層收到數(shù)據(jù)之后進行相應(yīng)的邏輯處理之后弛槐,將數(shù)據(jù)返回給view
層 -
view
層收到controller
層返回的數(shù)據(jù)之后,根據(jù)相應(yīng)的視圖模板和數(shù)據(jù)組裝之后依啰,返回一個渲染過的頁面 - 服務(wù)端將上一步的結(jié)果返回給客戶端
注:在以上的步驟中乎串,可能會存在執(zhí)行中間件的情況。
在對Express框架初始化的應(yīng)用缺點和MVC
有了簡單了解之后速警,接下來就是根據(jù)以上兩步的分析改進這個簡單的腳手架應(yīng)用了叹誉。
啟動文件和初始化文件合二為一
在Express
的官方文檔中關(guān)于app.listen()
方法的介紹中鸯两,提到:
Binds and listens for connections on the specified host and port. This method is identical to Node’s http.Server.listen().
所以我們完全可以使用app.listen()
方法替代bin/www
中的代碼邏輯,一行代碼替代一個應(yīng)用的啟動文件长豁,何樂而不為呢钧唐。
既然要合二為一,我們將程序的啟動文件和初始化文件都合并到app.js
中匠襟,這樣的話钝侠,就不要要導(dǎo)出app
對象給其他文件引入使用了,所以酸舍,首先刪除app.js
文件中的module.exports = app;
,然后机错,添加一行代碼即可:
app.listen(3000)
修改app.js
之后,訪問localhost:3000
,依然可以正常訪問到上面的頁面父腕,說明修改無誤。
抽離路由層
參照MVC
框架青瀑,路由層的工作是根據(jù)匹配客戶端的請求路徑匹配相應(yīng)的路由然后調(diào)用相應(yīng)的controller
璧亮,以下代碼為示例模板:
// 訪問首頁
app.get('/', IndexController.index)
在該小節(jié),我們僅僅抽離出路由層斥难,一步一步的來枝嘶。
首先,在項目根目錄下創(chuàng)建route.js
文件,該文件中的代碼如下:
module.exports = function (app) {
app.get('/', function(req, res, next) {
console.log('new index')
res.render('index', { title: 'Express' });
})
}
然后在app.js
中添加如下代碼:
var routes = require('./routes')
routes(app)
注:routes(app)
需要在創(chuàng)建應(yīng)用實例之后哑诊,即var app = express()
之后群扶。
同時,刪除app.js
中的// app.use('/', indexRouter)
镀裤。
最后竞阐,訪問localhost:3000
,依然可以正常訪問到上面的頁面,并且在控制臺看到輸出的new index
,說明修改無誤暑劝。
抽離controller層
首先骆莹,在根目錄下創(chuàng)建controller/index.js
,在index.js
中編寫如下代碼:
module.exports = {
index (req, res, next) {
console.log('controller')
res.render('index', { title: 'Express' });
}
}
然后担猛,將routes.js
修改為如下代碼:
var IndexController = require('./controller/index')
module.exports = function (app) {
app.get('/', IndexController.index)
}
最后,訪問localhost:3000
,依然可以正常訪問到上面的頁面,并且在控制臺看到輸出的controller
,說明修改無誤聊记。
抽離middleware層
首先浩蓉,在根目錄下創(chuàng)建middlewares/not-find.js
,在文件中編寫以下代碼:
var createError = require('http-errors');
module.exports = function(req, res, next) {
next(createError(404));
}
然后在app.js
中蒸走,引入相關(guān)中間件:
var notFind = require('./middlewares/not-find')
然后修改404錯誤處理中間件為:
// 404錯誤處理中間件
app.use(notFind);
最后仇奶,訪問localhost:3000/asasasasasa
,這是一個不存在的路徑,應(yīng)該會出現(xiàn)404頁面载碌,發(fā)現(xiàn)可以正常訪問到404頁面猜嘱,說明修改無誤衅枫。
其他自定義中間件的處理,同理朗伶。
小結(jié)
至此弦撩,我們對生成器工具express-generator
生成的Express應(yīng)用的改造完畢,并且接下來初始化自己的應(yīng)用可以不再使用express-generator
论皆,可以根據(jù)自己的需求益楼,參照以上修改,進行項目初始化搭建点晴。
下面附上項目的github地址:
我的個人博客: