Express 框架
第一章 Express簡介
npm提供了大量的第三方模塊嘶居,其中不乏許多Web框架麸锉,我們沒有必要重復(fù)發(fā)明輪子条辟,因而選擇使用 Express作為開發(fā)框架媒熊,因為它是目前最穩(wěn)定、使用最廣泛谜酒,而且Node.js官方推薦的唯一一個 Web開發(fā)框架叹俏。
Express是一個輕量級的Web框架,多數(shù)功能只是對HTTP協(xié)議中常用操作的封裝僻族,更多的功能需要插件或者整合其他模塊來完成.
Express中文官網(wǎng) http://www.expressjs.com.cn/
第二章 初始化一個Express應(yīng)用
- 我們想要安裝Express,首先需要安裝Epxress生成器,在全局通過命令安裝
npm install express-generator -g
- 等待安裝完畢粘驰,查看幫助信息:
express --help
可以看到屡谐,
Usage: express [options] [dir]
我們可以用express 在當(dāng)前目錄創(chuàng)建dir,并且依據(jù)配置選項
我們可以通過option設(shè)置express使用的模板蝌数,這里我們用ejs愕掏,也可以設(shè)置css的引擎,比如less和sass顶伞,這里我們先使用原生的css
所以只需要輸入
express myapp --view ejs
express會給我們快速的搭建一個app框架饵撑,然后根據(jù)提示進入myapp文件夾,運行
npm install
npm會自動幫助我們安裝當(dāng)前應(yīng)用所需要的模塊唆貌,等待安裝完畢之后滑潘,啟動:
npm start
瀏覽器打開:"localhost:3000",可以看到express框架給我返回的信息。
在調(diào)試過程中,如果我們不想反復(fù)的重啟服務(wù)端,可以npm安裝nodemon,使用nodemon啟動express項目
第三章 目錄分析
回到我們安裝express之前,看一下express快速搭建的目錄結(jié)構(gòu):
create : taobao
create : taobao/package.json //依賴關(guān)系文件
create : taobao/app.js // 入口文件
create : taobao/public //公共資源文件夾
create : taobao/public/images
create : taobao/public/stylesheets
create : taobao/public/stylesheets/style.css
create : taobao/public/javascripts
create : taobao/views //視圖文件夾
create : taobao/views/index.ejs
create : taobao/views/error.ejs
create : taobao/routes //路由文件夾
create : taobao/routes/index.js
create : taobao/routes/users.js
create : taobao/bin
create : taobao/bin/www //配置文件
由此可見,express是一個標(biāo)準(zhǔn)的MVC框架,通過路由分配視圖,并且可以在路由中寫上我們的控制結(jié)構(gòu).
修改 routes下的index.js
router.get('/', function(req, res, next) {
res.render('index', { title: '你好express' });
});
發(fā)現(xiàn)我們的頁面出現(xiàn)了改變,這個路由跟我們自己寫的路由作用是差不多的,具體用法我們后邊再談.
第四章 模板的使用方式
我們在安裝express的時候,使用的模板是ejs,具體什么是模板呢?ejs又怎么用呢?
簡單來講,模板就是幫助我們在view層進行輸出的工具.我們打開view下的index.ejs,可以看到,這個模板其實就是在ejs下寫了我們常見的html代碼,只是多了幾個不一樣的標(biāo)簽:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
<%= title %>
可以幫助我們將路由中的{ title: '你好express' }
輸出到模板當(dāng)中,其中的render就是模板渲染的方法,幫助我們渲染第一個參數(shù)index所對應(yīng)的模板.類似angular,我們也可以在標(biāo)簽內(nèi)部寫自己的表達式
<h1><%= title.toUpperCase() %></h1>
4.1 模板引擎的用法
ejs有三種常用的標(biāo)簽
-
<% code %>
:運行 JavaScript 代碼锨咙,不輸出 -
<%= code %>
:顯示轉(zhuǎn)義后的 HTML內(nèi)容 -
<%- code %>
:顯示原始 HTML 內(nèi)容
注意:
<%= code %>
和<%- code %>
都可以是JavaScript表達式生成的字符串语卤,當(dāng)變量code為普通字符串時,兩者沒有區(qū)別酪刀。當(dāng) code 比如為<h1>hello</h1>
這種字符串時粹舵,<%= code %>
會原樣輸出<h1>hello</h1>
,而<%- code %>
則會顯示H1大的hello字符串
下面的例子可以幫我們解釋<% code %>
的用法:
index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<ul>
<% for(var i=0; i<contents.length; i++) { %>
<li><%= contents[i] %></li>
<% } %>
</ul>
</body>
</html>
index.js
router.get('/', function(req, res, next) {
res.render('index',{
title: 'express',
contents:['AngularJS','Node.js','Express']
});
});
查看渲染之后的結(jié)果.
同樣,如果你想要動態(tài)的調(diào)整js代碼,可以將js代碼在<script>
標(biāo)簽中按照原樣渲染.
4.3 渲染模板的方法
我們剛剛看到,routes下的index.js使用render可以將后邊的參數(shù)渲染到模板之中,但是,我們在views下無法看到users.ejs,這是因為在user.js下使用了send方法,可以將后邊的信息直接渲染而不通過模板.
4.4 includes
我們使用模板引擎通常不是一個頁面對應(yīng)一個模板骂倘,這樣就失去了模板的優(yōu)勢齐婴,而是把模板拆成可復(fù)用的模板片段組合使用,如在 views 下新建 header.ejs 和 footer.ejs稠茂,并修改users.ejs
views/header.ejs
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
</style>
</head>
<body>
views/footer.ejs
</body>
</html>
views/users.ejs
<%- include('header') %>
<h1><%= name.toUpperCase() %></h1>
<p>hello, <%= name %></p>
<%- include('footer') %>
routes/users.js
router.get('/', function(req, res, next) {
// res.send('respond with a resource');
res.render('users',{
name: 'express'
});
});
訪問'localhost:3000/users'可以看到渲染后的結(jié)果.
我們將users.ejs拆成出了header.ejs和footer.ejs,并在users.ejs通過 ejs 內(nèi)置的 include方法引入柠偶,從而實現(xiàn)了跟以前一個模板文件相同的功能.
注意:要用
<%- include('header') %>
而不是<%= include('header') %>
第五章 路由
我們在路由中將數(shù)據(jù)渲染并且分配到模板當(dāng)中,那么我們?nèi)绾螌懸粋€路由呢?
5.1 入口文件
首先,我們先要了解入口文件的組成,打開app.js,我們可以看到
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
這幾句話表示我們引入的模塊
var index = require('./routes/index');
var users = require('./routes/users');
路由都是模塊寫法,而入口文件將這些路由引入,我們就可以直接使用了.
app.use('/', index);
app.use('/users', users);
這則使用了我們的路由
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
這兩句話設(shè)置了我們的模板引擎,如果我們想要使用sass或者less等css引擎,同樣可以向下追加.
// catch 404 and forward to error handler
...
// error handler
...
剩下的部分則是規(guī)定了404和500錯誤下的頁面渲染方法.
由此,我們可以在routes下添加的路由文件,并且在入口文件引入
5.2 新增路由
在routes下添加新的路由:
routers/tests.js
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
res.send('這是新的路由');
});
module.exports = router;
同樣,在入口文件添加
app.js
var tests = require('./routes/tests');
app.use('/tests', tests);
訪問'localhost:3000/tests'則可以獲取我們新增加的路由信息.
5.3 路由中的參數(shù)
其中 req,res是我們熟悉的請求和響應(yīng)對象,當(dāng)我們的地址欄訪問模板的時候,可以獲取參數(shù)
ruoter.get('/:name', function(req, res, next) {
res.send('hello, ' + req.params.name);
});
我們在地址欄請求'localhost:3000/tests/Y18',我們可以看到響應(yīng)信息
hello, Y18
不難看出:req 包含了請求來的相關(guān)信息,res 則用來返回該請求的響應(yīng)睬关。下面介紹幾個常用的
req 的屬性:
-
req.query
: 解析后的 url 中的 querystring诱担,如?name=haha
,req.query 的值為{name: 'haha'}
-
req.params
: 解析 url 中的占位符电爹,如/:name
蔫仙,訪問 /haha,req.params 的值為{name: 'haha'}
-
req.body
: 解析后請求體丐箩,需使用相關(guān)的模塊摇邦,如 body-parser,請求體為{"name": "haha"}
屎勘,則 req.body 為{name: 'haha'}
,我們可以用接受post請求為例. - 注意施籍,無論是send或者render,我們只能獲取第一次符合標(biāo)準(zhǔn)的渲染頁面
5.4 next和中間件
前面我們講解了express中路由和模板引擎ejs的用法概漱,但express的精髓并不在此丑慎,在于中間件的設(shè)計理念。
我們可以看到,在路由的回調(diào)函數(shù)中竿裂,有一個參數(shù)next玉吁,這就是我們使用中間件最重要的部分。
express中的中間件(middleware)是用來處理請求的腻异,當(dāng)一個中間件處理完进副,可以通過調(diào)用 next()
傳遞給下一個中間件,如果沒有調(diào)用 next()
悔常,則請求不會往下傳遞影斑,如內(nèi)置的 res.render
其實就是渲染完 html 直接返回給客戶端,如果調(diào)用了next()
这嚣,那么請求會繼續(xù)向下傳遞鸥昏,訪問路由組中的下一個路由塞俱。
next中間件分為應(yīng)用級中間件和路由級中間件
應(yīng)用級中間件
我們回頭再來看一下入口文件:
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index);
app.use('/users', users);
app.use('/tests', tests);
其實,這些app.use()也是給我們加載中間件,只是這些中間件是在應(yīng)用中使用的,應(yīng)用級中間件綁定到 app 對象 使用 app.use() 和 app.METHOD()姐帚, 其中, METHOD 是需要處理的 HTTP 請求的方法障涯,例如 GET, PUT, POST 等等罐旗,全部小寫。我們自定義一下全局中間件:
// 掛載到全局的中間件,任何請求都會執(zhí)行它
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 掛載至 /hello/:name 的中間件唯蝶,任何指向 /hello/:name 的請求都會執(zhí)行它
app.use('/hello/:name', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 路由和句柄函數(shù)(中間件系統(tǒng))九秀,處理指向 /hello/:name 的 GET 請求
app.get('/hello/:name', function (req, res, next) {
res.send('hello '+ req.params.name);
});
如果我們不指定路徑,則默認匹配所有路徑,我們在最后一個路由中并沒有使用next,所以請求沒有向下傳遞.
我們在上方引入的其余全局中間件,即第三方中間件,其實也在回調(diào)函數(shù)的末尾使用了next方法,使得請求能夠繼續(xù)向下傳遞.
在我們訪問'localhost:3000/hello/Y18',可以在控制臺和頁面都能看到數(shù)據(jù)返回.
注意:
作為中間件系統(tǒng)的路由句柄,使得為路徑定義多個路由成為可能粘我。
// 一個中間件棧鼓蜒,處理指向 /user/:id 的 GET 請求
app.get('/hello/:id', function (req, res, next) {
console.log('ID:', req.params.id);
next();
}, function (req, res, next) {
res.send('hello Info');
});
// 處理 /user/:id, 打印出用戶 id
app.get('/hello/:id', function (req, res, next) {
res.end(req.params.id);
});
但是由于第一個路由已經(jīng)終止了請求-響應(yīng)循環(huán),第二個路由雖然不會帶來任何問題征字,但卻永遠不會被調(diào)用.
如果需要在中間件棧中跳過剩余中間件都弹,調(diào)用 next('route') 方法將控制權(quán)交給下一個路由
// 一個中間件棧,處理指向 /user/:id 的 GET 請求
app.get('/hello/:id', function (req, res, next) {
console.log('ID:', req.params.id);
if(req.params.id == 4) next('route');
else next();
}, function (req, res, next) {
res.send('hello Info');
});
// 處理 /user/:id匙姜, 打印出用戶 id
app.get('/hello/:id', function (req, res, next) {
res.end(req.params.id);
});
路由級中間件
路由級中間件和應(yīng)用級中間件一樣畅厢,只是它綁定的對象為 express.Router()
修改index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/:name', function(req, res, next) {
// 如果 user id 為 0, 跳到下一個路由
if (req.params.name != 'Y18') next('route');
// 否則將控制權(quán)交給棧中下一個中間件
else next();
}, function (req, res, next) {
// 渲染常規(guī)頁面
res.send('你別來了,Y18');
});
// 處理name為Y18的路由氮昧, 渲染一個特殊頁面
router.get('/:name', function (req, res, next) {
res.send('歡迎你~ '+ req.params.name);
});
module.exports = router;
錯誤處理中間件
當(dāng)我們訪問一個不存在的路由時,雖然我們最后一個路由沒有寫next中間件,但是錯誤路由依舊執(zhí)行,查看源碼:
https://github.com/expressjs/express/blob/master/lib/router/index.js
function next(err) {
... //此處源碼省略
// find next matching layer
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path);
route = layer.route;
if (typeof match !== 'boolean') {
// hold on to layerError
layerError = layerError || match;
}
if (match !== true) {
continue;
}
... //此處源碼省略
}
... //此處源碼省略
// this should be done for the layer
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
next函數(shù)內(nèi)部有個while循環(huán)框杜,每次循環(huán)都會從stack中拿出一個layer,這個layer中包含了路由和中間件信息袖肥,然后就會用layer和請求的path就行匹配咪辱,如果匹配成功就會執(zhí)行l(wèi)ayer.handle_request,調(diào)用中間件函數(shù)椎组。但如果匹配失敗梧乘,就會循環(huán)下一個layer(即中間件)。
express有成百上千的第三方中間件,在開發(fā)過程中我們首先應(yīng)該去npm上尋找是否有類似實現(xiàn)的中間件选调,盡量避免造輪子夹供,節(jié)省開發(fā)時間。下面給出幾個常用的搜索 npm 模塊的網(wǎng)站:
第六章 在express中使用less或sass
如果我們想要使用sass或者less,首先要對其進行學(xué)習(xí);
Express支持sass,less等css引擎,我們現(xiàn)在使用sass編譯模板的樣式:
重新安裝一個express應(yīng)用:express app --view ejs -c sass
我們可以看到在入口文件中看到變化:
app.use(require('node-sass-middleware')({
src: path.join(__dirname, 'public'),
dest: path.join(__dirname, 'public'),
indentedSyntax: true,
sourceMap: true
}));
這其實就是引入了node的sass中間件仁堪。
同時哮洽,在public/stylesheets中可以看到style.sass這個sass文件。
啟動express弦聂,訪問'localhost:8000/'我們可以看到在這個文件夾中生成了兩個新的文件:
'style.css'
'style.css.map'
這就是我們熟悉的sass經(jīng)過編譯后的文件鸟辅。
同樣,我們可以寫自己的sass文件莺葫,決定樣式:
修改style.sass
$fontStack: Helvetica, sans-serif;
$primaryColor: #8A2BE2;
body
font-family: $fontStack;
padding: 50px;
p
color: $primaryColor;
ul li
color: #BDB76B;
a
text-decoration: none;
修改index.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
<ul>
<li>AngularJS</li>
<li>Node.js</li>
<li>Express</li>
</ul>
<a id="test" href="www.zixue.it">自學(xué)it網(wǎng)</a>
</body>
</html>
訪問'localhost:3000',可以看到我們修改之后的樣式.
我們可以修改indentedSyntax改為false,實現(xiàn)scss方式的書寫