Express4入門指南

2018-06-09 17:18 更新

Express是基于Nodejs以及一系列Nodejs第三方package的一款Web開發(fā)框架。

Express經(jīng)歷過2.x,3.x以及最新的4.x版本。Express各個(gè)版本的差異還是比較大的?,F(xiàn)在2.x版本官方已經(jīng)不再維護(hù)(deprecated),3.x版本雖然可以使用,但是官方建議升級(jí)到4.x版本。如果你之前使用的express 3.x版本,官方也有給出遷移到4.x的指南

本文圍繞express的Routing和Middleware機(jī)制,作一些討論。主要參考express官網(wǎng)的guide。

安裝和基本示例

我們首先從最基本的講起。首先我們得有一個(gè)nodejs項(xiàng)目,


> mkdir test & cd test
> npm init

我一般習(xí)慣使用npm init來創(chuàng)建一個(gè)nodejs項(xiàng)目。因?yàn)樗梢蕴峁┮粋€(gè)交互式的命令行來生成最基本的package.json文件。

在創(chuàng)建好package.json文件之后,接下來我們就需要安裝express了,


> npm install express --save

我們使用--save選項(xiàng)自動(dòng)更新package.jsondependencies字段。在express安裝結(jié)束之后,package.json就變成了下面這樣了,


{
  "name": "test",
  "version": "1.0.0",
  "description": "kick off express4",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "nodejs"
  ],
  "author": "gejiawen",
  "license": "MIT",
  "dependencies": {
    "express": "^4.13.3"
  }
}

根據(jù)你系統(tǒng)的差異或者網(wǎng)絡(luò)因素,你可能需要使用sudo npm install express --save,或者是sudo cnpm install express --save。

使用npm安裝包的時(shí)候,除了--save選項(xiàng),我們還可以使用--save-dev。兩者在安裝完包之后,都可以自動(dòng)更新package.json文件,不同的是,前者更新的是dependencies字段,后者更新的是devDependencies字段。而dependenciesdevDependencies的區(qū)別可以簡單的理解成,前者是生產(chǎn)環(huán)境所需要的依賴,而后者是開發(fā)環(huán)境所需要的依賴。

在安裝完畢express之后,我們需要新建一個(gè)啟動(dòng)文件,就是package.jsonmain字段指定的文件。當(dāng)然你說我能不能新建一個(gè)文件叫別的什么名字,那當(dāng)然也是沒問題的。


> touch index.js

index.js文件的內(nèi)容如下,


var express = require('express');
var app = express();
app.get('/', function (req, res) {
    res.send('Hello world!');
});
app.listen(3000, function () {
    console.log('app is listening at localhost:3000');
});

最后再啟動(dòng)這個(gè)文件,


> node index.js

至此,我們使用express開發(fā)的一個(gè)最最簡單的web程序就完成了。可以我瀏覽器輸入localhost:3000,看看效果吧。

工作原理

經(jīng)過上面的簡單示例,看起來使用express做web真的非常簡單。為什么可以做到這么簡單呢?這是跟express的設(shè)計(jì)理念有關(guān)系的。

整個(gè)express應(yīng)用可以簡單的抽象成一系列的路由和中間件。當(dāng)然這些路由和中間件之間存在著調(diào)用和先后關(guān)系。

那么,路由和中間件具體又是指的是什么呢?

簡單來說,express中的路由可以看成一種path watcher,比如上面示例中的代碼,


app.get('/', function (req, res) {
    res.send('Hello world!');
});

這段代碼中,express app將監(jiān)控/路徑,來自客戶端所有對/路徑的請求,都使用后面的回調(diào)函數(shù)處理。

而這里的處理請求的回調(diào)函數(shù)其實(shí)就是所謂的中間件。

所以,中間件其實(shí)就是處理HTTP請求的回調(diào)函數(shù),一般來說它會(huì)有3個(gè)參數(shù),分別是req,resnext,分別表示請求對象,響應(yīng)對象以及next回調(diào)。next回調(diào)的作用是將控制權(quán)傳遞給下一個(gè)中間件。

如果一個(gè)中間件是專門用于錯(cuò)誤處理的,它將會(huì)有4個(gè)參數(shù),分別是errreq,res,next。其中第一個(gè)參數(shù)表示錯(cuò)誤對象。

中間件的基本執(zhí)行過程是這樣的,在中間件內(nèi)部對req和res對象進(jìn)行處理加工,執(zhí)行相關(guān)邏輯,然后根據(jù)業(yè)務(wù)決定是否執(zhí)行next()函數(shù),傳遞到下一個(gè)中間件。

除此之外,一般我們建議一個(gè)中間件只專注做一件事,也就是說中間件的職責(zé)盡量單一,這樣利于維護(hù)和協(xié)調(diào)中間件。

中間件機(jī)制

下面我們來詳細(xì)的說一說express的中間件機(jī)制。

其實(shí)所謂的中間件就是一個(gè)函數(shù)回調(diào)。express中采用use來注冊中間件。比如,


var expresss = require('express');
var app = express();
app.use(function (req, res, next) {
    console.log('Time: ', new Date());
    next();
});
// more code...

如上例所示,這個(gè)中間件是整個(gè)app的第一個(gè)中間件,當(dāng)有請求過來時(shí),它將會(huì)被第一個(gè)調(diào)用,在console中打印出時(shí)間信息。執(zhí)行完畢之后,通過next()將執(zhí)行權(quán)傳遞給后面的中間件。

此外,我們可以再中間件內(nèi)容做一些額外的事情,比如對請求路徑的判斷,


app.use(function(req, res, next) {
    if (request.url === "/") {
        response.writeHead(200, { "Content-Type": "text/plain" });
        response.end("Welcome to the homepage!\n");
    } else {
        next();
    }
});
app.use(function(req, res, next) {
    if (req.url === "/about") {
        res.writeHead(200, { "Content-Type": "text/plain" });
        res.end("about page!\n");
    } else {
        next();
    }
});
app.use(function(req, res, next) {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("404 error!\n");
})

上面的代碼表示,

  • 如果請求的是/路徑,返回給客戶端Welcome to the homepage!。如果是其他路徑,就調(diào)用下一個(gè)中間件。
  • 第二個(gè)中間件中,判斷路徑是否是/about,如果是,將返回給客戶端about page!。如果不是,調(diào)用下一個(gè)中間件。
  • 當(dāng)執(zhí)行權(quán)傳遞到第三個(gè)中間件時(shí),那么客戶端請求的路徑不是/也不是/about,此時(shí)我們將給出一個(gè)404 error!的提示,并同時(shí)終止中間件的繼續(xù)調(diào)用。

從上面的示例代碼,我們足以管中窺豹,領(lǐng)略一下express中間件機(jī)制,以及它們的調(diào)用策略。簡單來說就是,express按照順序執(zhí)行中間件,每個(gè)中間件做好自己的任務(wù)并決定是否調(diào)用下一個(gè)中間件。

中間件的分類

express的官網(wǎng)上對中間件作了個(gè)分類,包含如下幾種,

應(yīng)用級(jí)別的中間件其實(shí)就是指掛在app上的中間件。通常我們除了可以通過app.use來掛載中間件之外,express還提供了app.METHOD這種方式。這里的METHOD指的是http的方法。比如

  • app.get
  • app.post
  • app.put

除此之外,還有一個(gè)特別的動(dòng)詞我們也可以使用,就是app.all(),它表示所有的請求都會(huì)通過這個(gè)中間件??聪旅娴氖纠a,


app.all("*", function(request, response, next) {
    response.writeHead(200, { "Content-Type": "text/plain" });
    next();
});
app.get("/", function(request, response) {
    response.end("Welcome to the homepage!");
});
app.get("/about", function(request, response) {
    response.end("Welcome to the about page!");
});
app.get("*", function(request, response) {
    response.end("404!");
});

所有的請求都將會(huì)執(zhí)行第一個(gè)中間,用戶設(shè)置http響應(yīng)的頭信息。如果請求的是/或者/about,則執(zhí)行相應(yīng)的中間件。這里注意,第二個(gè)和第三個(gè)中間件都沒有調(diào)用next(),所以他們執(zhí)行完畢之后,并不會(huì)調(diào)用后面的中間件。如果請求的路徑不是/或者/about,那么第二個(gè)和第三個(gè)中間件都將不會(huì)被調(diào)用,第四個(gè)中間件將會(huì)被調(diào)用。

所以說,express應(yīng)用中中間件的順序是很重要的,它將影響中間件的執(zhí)行順序。

Router機(jī)制

前面我們知道,可以通過app.use或者是app.METHOD('/')來配置路由。

在Express4中,路由成了一個(gè)單獨(dú)的組件,express提供了一個(gè)新的對象express.Router,可以通過它來更好的配置路由。我們先來看一個(gè)示例,


// birds.js
var express = require('express');
var router = express.Router();
router.use(function (req, res, next) {
    console.log('Time: ', new Date());
    next();
});
router.get('/', function (req, res) {
    res.send('Birds home page');
});
router.get('/about', function (req, res) {
    res.send('Birds about page');
});
module.exports = router;


// index.js
var express = require('express');
var birds = require('./birds');
var app = express();
app.get('/', function (req, res) {
    res.send('app home page');
});
app.use('/birds', birds);
app.listen(3000, function () {
    console.log('server starting...');
});

通過這個(gè)簡單的例子,我們可以獨(dú)立封裝一組路由成為一個(gè)路由中間件,在主文件中可以根據(jù)需要掛在到不同的路徑上,如app.use('/birds', birds)。這樣一來,路由中間件中對路由的配置最終會(huì)被解析成如下,

  • /birds/
  • /birds/about/

我們使用express進(jìn)行web開發(fā)的時(shí)候,一般也是推薦建立一個(gè)routes文件夾,將所有頁面的路徑配置都抽象成一個(gè)路由中間件,然后在主文件中掛載。

模式匹配和參數(shù)

使用進(jìn)行路由配置的時(shí)候,我們不僅僅可以使用字符常量,還可以使用字符串的模式匹配,如下,


// will match acd and abcd
app.get('/ab?cd', function(req, res) {
    res.send('ab?cd');
});
// will match abcd, abbcd, abbbcd, and so on
app.get('/ab+cd', function(req, res) {
    res.send('ab+cd');
});
// will match abcd, abxcd, abRABDOMcd, ab123cd, and so on
app.get('/ab*cd', function(req, res) {
    res.send('ab*cd');
});
// will match /abe and /abcde
app.get('/ab(cd)?e', function(req, res) {
    res.send('ab(cd)?e');
});
// will match anything with an a in the route name:
app.get(/a/, function(req, res) {
    res.send('/a/');
});
// will match butterfly, dragonfly; but not butterflyman, dragonfly man, and so on
app.get(/.*fly$/, function(req, res) {
    res.send('/.*fly$/');
});

如你所見,路由的模式匹配其實(shí)很簡單。你可以使用正則,讓你的路由回調(diào)更加隨心所欲的匹配不同的路徑。

路由參數(shù)的意思就是你可以在路由配置時(shí)定義好路徑中帶有的參數(shù),比如


app.get('/book/:bookId', function (req, res, next) {
    console.log(req.params);
});

這樣你就可以在回調(diào)中,處理req中的params對象。

路由回調(diào)

有時(shí)候我們可能會(huì)有這樣一個(gè)需求,我需要在路由回調(diào)中進(jìn)行一些預(yù)處理。比如下面這個(gè)例子


var express = require('express');
var app = express();
app.get('/user/:id', function (req, res, next) {
    if (req.params.id === 0) {
        next('route');
    } else {
        next();
    }
}, function (req, res, next) {
    res.send('regular');
});
app.get('/user/:id', function (req, res, next) {
    res.send('special');
});
app.listen(3000);

上面的示例代碼,有兩點(diǎn)需要注意,

1.我們可以在路由配置的回調(diào)中,添加多個(gè)回調(diào)函數(shù)。如果有多個(gè)回調(diào),我們可以有兩種形式,如下,


app.get('/', function() {}, function() {});
app.get('/', [callback1, callback2, callback3]);

2.同一個(gè)路由配置的多個(gè)回調(diào)函數(shù)在執(zhí)行時(shí)可以被跳過。

上面代碼中的next('route')表示跳過當(dāng)前路由中間件中剩下的路由回調(diào),執(zhí)行下一個(gè)中間件。而next()表示執(zhí)行當(dāng)前中間件的下一個(gè)路由回調(diào)。所以,當(dāng)我請求/user/100時(shí),返回的是regular。當(dāng)我請求/user/0時(shí),返回的是special。

app.router()

在express4中我們可以使用app.router對同一個(gè)路徑做不同的請求方法配置,如下,


app.route('/book')
    .get(function(req, res) {
        res.send('Get a random book');
    })
    .post(function(req, res) {
        res.send('Add a book');
    })
    .put(function(req, res) {
        res.send('Update the book');
    });

其實(shí)這種方式在路由中間件中也是可以用的。針對特定的路由定制場景是特別適用的,可以更好的模塊化、節(jié)省一些冗余的代碼。

模板引擎

之前我們的示例代碼中,都是簡單是使用res.send給客戶端發(fā)送簡單的文本。如果我們想要客戶端渲染一個(gè)自定義的頁面,那該怎么做呢?

這就需要用到模板了。嚴(yán)格來說,express使用的模板屬于服務(wù)端模板,因?yàn)樗窃诜?wù)端解析完畢,發(fā)送給客戶端進(jìn)行渲染的。express支持很多模板引擎,基本上將市面上常見模板引擎一網(wǎng)打盡了。express默認(rèn)的模板引擎是jade,一款非常優(yōu)雅的模板引擎。后面博主會(huì)有文章介紹jade。

言歸正傳,我們在express中如何使用模板引擎來加載模板呢?其實(shí)很簡單,三步走即可,

  1. 設(shè)置好模板的靜態(tài)路徑
  2. 設(shè)置好渲染的模板引擎
  3. 在需要的地方渲染你的模板

具體看下面的示例代碼,


// 省略無關(guān)代碼
// 設(shè)置模板文件夾的路徑
app.set('views', path.join(__dirname, 'views'));
// 設(shè)置模板文件的后綴名
app.set('view engine', 'jade');
app.get('/', function (req, res){
    res.render('index');
});
app.get('/about', function(req, res) {
    res.render('about');
});
app.get('/article', function(req, res) {
    res.render('article');
});
// 省略無關(guān)代碼

我們在views目錄下有3個(gè)模板,分別為views/index.jade,views/about.jade,views/article.jade,當(dāng)請求不同的路徑時(shí),我們會(huì)發(fā)送不同的模板給前端渲染。

如果你使用的是別的模板引擎,請參閱模板引擎針對express的相關(guān)說明。

本文由于篇幅原因,不會(huì)關(guān)注express的方方面面,更多的內(nèi)容請參考官網(wǎng)的API Reference

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)