Express4入門指南

Express 是基於 Nodejs 以及一系列Nodejs第三方package的一款Web開發框架。

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

本文圍繞express的Routing和Middleware機制,作一些討論。主要參考express官網的guide。

安裝和基本示例

我們首先從最基本的講起。首先我們得有一個nodejs項目,

> mkdir test & cd test
> npm init

我一般習慣使用 npm init 來創建一個nodejs項目。因爲它可以提供一個交互式的命令行來生成最基本的 package.json 文件。

在創建好 package.json 文件之後,接下來我們就需要安裝express了,

> npm install express --save

我們使用 –save 選項自動更新 package.json 的 dependencies 字段。在express安裝結束之後, 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"
  }
}

根據你係統的差異或者網絡因素,你可能需要使用 sudo npm install express –save ,或者是 sudo cnpm install express –save 。

使用npm安裝包的時候,除了 –save 選項,我們還可以使用 –save-dev 。兩者在安裝完包之後,都可以自動更新 package.json 文件,不同的是,前者更新的是 dependencies 字段,後者更新的是 devDependencies 字段。而 dependencies 和 devDependencies 的區別可以簡單的理解成,前者是生產環境所需要的依賴,而後者是開發環境所需要的依賴。

在安裝完畢express之後,我們需要新建一個啓動文件,就是 package.json 中 main 字段指定的文件。當然你說我能不能新建一個文件叫別的什麼名字,那當然也是沒問題的。

> touch index.js

index.js 文件的內容如下,

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');
});

最後再啓動這個文件,

> node index.js

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

工作原理

經過上面的簡單示例,看起來使用express做web真的非常簡單。爲什麼可以做到這麼簡單呢?這是跟express的設計理念有關係的。

整個express應用可以簡單的抽象成一系列的路由和中間件。當然這些路由和中間件之間存在着調用和先後關係。

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

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

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

這段代碼中,express app將監控 / 路徑,來自客戶端所有對 / 路徑的請求,都使用後面的回調函數處理。

而這裏的處理請求的 回調函數 其實就是所謂的中間件。

所以,中間件其實就是 處理HTTP請求 的回調函數,一般來說它會有3個參數,分別是 req , res 和 next ,分別表示請求對象,響應對象以及next回調。next回調的作用是將控制權傳遞給下一個中間件。

如果一箇中間件是專門用於錯誤處理的,它將會有4個參數,分別是 err , req , res , next 。其中第一個參數表示錯誤對象。

中間件的基本執行過程是這樣的, 在中間件內部對req和res對象進行處理加工,執行相關邏輯,然後根據業務決定是否執行 next() 函數,傳遞到下一個中間件 。

除此之外,一般我們建議一箇中間件只專注做一件事,也就是說中間件的職責儘量單一,這樣利於維護和協調中間件。

中間件機制

下面我們來詳細的說一說express的中間件機制。

其實所謂的中間件就是一個函數回調。express中採用 use 來註冊中間件。比如,

var expresss = require('express');
var app = express();

app.use(function (req, res, next) {
    console.log('Time: ', new Date());
    next();
});

// more code...

如上例所示,這個中間件是整個app的第一個中間件,當有請求過來時,它將會被第一個調用,在console中打印出時間信息。執行完畢之後,通過 next() 將執行權傳遞給後面的中間件。

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

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! 。如果是其他路徑,就調用下一個中間件。
第二個中間件中,判斷路徑是否是 /about ,如果是,將返回給客戶端 about page! 。如果不是,調用下一個中間件。
當執行權傳遞到第三個中間件時,那麼客戶端請求的路徑不是 / 也不是 /about ,此時我們將給出一個 404 error! 的提示,並同時終止中間件的繼續調用。
從上面的示例代碼,我們足以管中窺豹,領略一下express中間件機制,以及它們的調用策略。簡單來說就是, express按照順序執行中間件,每個中間件做好自己的任務並決定是否調用下一個中間件 。

中間件的分類

express的官網上對中間件作了個分類,包含如下幾種,

Application-level middleware (應用級別中間件)
Router-level middleware (路由級別中間件)
Error-handling middleware (錯誤處理中間件)
Built-in middleware (內置中間件)
Third-party middleware (第三方中間件)
應用級別的中間件其實就是指掛在 app 上的中間件。通常我們除了可以通過 app.use 來掛載中間件之外,express還提供了 app.METHOD 這種方式。這裏的 METHOD 指的是http的方法。比如

app.get
app.post
app.put
除此之外,還有一個特別的動詞我們也可以使用,就是 app.all() ,它表示所有的請求都會通過這個中間件。看下面的示例代碼,

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!");
});

所有的請求都將會執行第一個中間,用戶設置http響應的頭信息。如果請求的是 / 或者 /about ,則執行相應的中間件。這裏注意,第二個和第三個中間件都沒有調用 next() ,所以他們執行完畢之後,並不會調用後面的中間件。如果請求的路徑不是 / 或者 /about ,那麼第二個和第三個中間件都將不會被調用,第四個中間件將會被調用。

所以說,express應用中中間件的順序是很重要的,它將影響中間件的執行順序。

Router機制

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

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

// 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...');
});

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

/birds/
/birds/about/
我們使用express進行web開發的時候,一般也是推薦建立一個routes文件夾,將所有頁面的路徑配置都抽象成一個路由中間件,然後在主文件中掛載。

模式匹配和參數

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

// 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$/');
});

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

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

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

這樣你就可以在回調中,處理 req 中的 params 對象。

路由回調

有時候我們可能會有這樣一個需求,我需要在路由回調中進行一些預處理。比如下面這個例子

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);

上面的示例代碼,有兩點需要注意,

1.我們可以在路由配置的回調中,添加多個回調函數。如果有多個回調,我們可以有兩種形式,如下,

app.get('/', function() {}, function() {});

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

2.同一個路由配置的多個回調函數在執行時可以被跳過。

上面代碼中的 next(‘route’) 表示 跳過 當前路由中間件中剩下的路由回調,執行下一個中間件。而 next() 表示執行當前中間件的下一個路由回調。所以,當我請求 /user/100 時,返回的是regular。當我請求 /user/0 時,返回的是special。

app.router()

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

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');
    });

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

模板引擎

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

這就需要用到模板了。嚴格來說,express使用的模板屬於服務端模板,因爲它是在服務端解析完畢,發送給客戶端進行渲染的。express支持很多 模板引擎 ,基本上將市面上常見模板引擎一網打盡了。express默認的模板引擎是 jade ,一款非常優雅的模板引擎。後面博主會有文章介紹jade。

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

設置好模板的靜態路徑
設置好渲染的模板引擎
在需要的地方渲染你的模板
具體看下面的示例代碼,

// 省略無關代碼

// 設置模板文件夾的路徑
app.set('views', path.join(__dirname, 'views'));
// 設置模板文件的後綴名
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');
});

// 省略無關代碼

我們在 views 目錄下有3個模板,分別爲 views/index.jade , views/about.jade , views/article.jade ,當請求不同的路徑時,我們會發送不同的模板給前端渲染。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章