$ express $ express /tmp/foo && cd /tmp/foo
程式碼如下:
$ npm install -d
複製碼> 程式碼如下:
$ node app.js
三、建立一個伺服器
要建立一個 express.HTTPServer 實例,只需呼叫 createServer() 方法。 一般這個應用實例,我們可以定義基於HTTP 動作(HTTP Verbs)的路由,以app.get() 為例: 複製程式碼
var app = require('express').createServer();
app.get('/', function(req, res){ res.send('hello world'); });
app.listen(3000);
四、建立一個 HTTPS 伺服器
如上述初始化一個 express.HTTPSServer 實例。然後我們給它一個配置對象,接受 key、cert 和其他在 https 文件 所提到的(屬性/方法)。
複製程式碼 程式碼如下:
var app = require('>
var app = require('>.createServer( { key: ... });
下面這個範例只在開發階段 dumpExceptions (拋錯),並傳回堆疊異常。不過在兩個環境中我們都使用 methodOverride 和 bodyParser。注意一下 app.router 的使用,它可以(可選)用來載入(mount)程式的路由,另外首次呼叫 app.get()、app.post() 等也會載入路由。
複製程式碼
程式碼如下: app.configure(function(){ app.use(express.bodyParser());
app.use(app.router); });
app.configure('development', function(){ app.use(express.static(__dirname '/public'));
app.use(express.errorHandler({n dumpions: true,true, showStack: true })); });
複製程式碼
程式碼如下:app.configure('stage', 'prod', function(){ // config }); 對於任何內部設定(#),Express 提供了 set(key[, val])、 enable(key) 和 disable(key) 方法:
譯註:設定詳見:application.js 的 app.set。
app.configure(function(>
app.configure(function(>
app.configure(function()){
.set('views', __dirname '/views'); app.set('views');
// => "/absolute/path/to/views"
app.enable('some feature');
// 等價於:app.set('some feature', true);
app.disable('some feature'); // 等價於:app.set('some feature', false);
app.enabled('some feature') // => false複製代碼
代碼如下:$ NODE_ENV=production node app.js 這很重要,因為多數緩存機制只在產品階段是被開啟的。
六、設定
Express 支援下列快速(out of the box)設定:
1.basepath 用於res.redirect() 的應用程式基本路徑(base path),明確處理綁定的應用程式(transparently handling mounted apps.) 2.view View 預設的根目錄為CWD/views 3.view engine 預設View 引擎處理(View 檔案)並不需要使用後綴 4.view cache 啟用View 快取(在產品階段啟用)
5.charet 改變編碼,預設為utf-86.case sensitive routes 路由中區分大小寫7.strit routing 啟用後(路由中的)結尾/ 將不會被忽略(譯註:即app.get('/sofish ') 和app.get('/sofish/') 將是不一樣的) 8.json callback 啟用res.send() / res.json() 明確的jsonp 支援(transparent jsonp support)
七、路由
複製程式碼
程式碼如下:
app.get('/user/:id', function(req, res){ res.send('user ' req.params.id);複製程式碼
程式碼如下:
// 修改這個官方的字串複製程式碼
程式碼如下:
app.get(/^/users?(>app.get(/^/users?(?: /(d )(?:..(d ))?)?/, function(req, res){
res.send(req.params);}); Curl針對上述定義路由的請求:
複製程式碼 程式碼如下: $ curl:$ /dev:3000/user [null,null]
$ curl http://dev:3000/users [null,null] $ curl http://dev:3000/users /1複製程式碼 程式碼如下:
"/user/:id"
/user/12
"/users/:id?" /users/5 /users
"/files/*" /files/jquery.js /files/javascripts/jquery.js
"/file/*.*" /files/jquery.js /files/javascripts/jquery.js
"/user/:id/:operation?" /user/1 /user/1/edit
"/products.:format" /products.json /products.xml
"/products.:format?" /products.json /products.xml /products
"/user/:id.:format?" /user/12 /user/12.json
舉個例子,我們可以使用POST 發送json 數據,透過bodyParser這個可以解析json 請求內容(或其他內容)的中間件來傳回數據,並將回傳結果存於req.body 中:
var express = require('express')
, app = express.createServer();
app.use(express.bodyParser());
app.post('/', function(req, res){ res.send(req.body); });
app.listen(3000);
通常我們可以使用一個像 user/:id 這樣,沒有(命名)限制的「傻瓜」式的佔位符。然而比方說,我們要限制用戶id 只能是數字,那麼我們可能使用/user/:id([0-9] ),這個將僅當佔位符是包含至少一位數字時才生效(適配,match)。
八、進路控制(Passing Route Control)
我們可以透過呼叫第三個參數,next() 函數,來控制下一個適配的路由。如果找不到適配,控制權將會傳回給 Connect,同時中間件將會依 use() 中新增的順序依序呼叫。道理同樣適應多個定義到同一路徑的路由,他們將會依序被呼叫直到其中某個不呼叫 next() 而決定做出請求回應。
app.get('/users/:id?' , function(req, res, next){
var id = req.params.id;
if (id) {
ext();
}
});
app.get('/users', function(req, res){ // do something else
}); app.all() 方法只呼叫一次就可以方便地把同樣的邏輯到所有HTTP 動作。下面我們使用它來從偽數據中提取一個用戶,將其賦給 req.user。
var express = require('express')
var express = require('express')
var express = require('express')
var users = [{ name: 'tj' }];
app.all('/user/:id/:op?', function(req, res, next){ req.user = users[req.params.id]; if (req .user) { next(); } else { next(new Error('cannot find user ' req.params.id)); }
}; >
app.get('/user/:id', function(req, res){ res.send('viewing ' req.user.name); });
app.get('/user/:id/edit', function(req, res){ res.send('editing ' req.user.name); });
app.put('/user/:id', function(req, res){ res.send('updating ' req.user.name); });
app.get('*', function(req, res){ res.send(404, 'what???'); });
app.listen(3000);
九、中間件
使用的 Connect 中間件(屬性)通常伴隨著你的一個常規 Connect 伺服器,被傳到 express.createServer() 。如:
var express = require('
var express = require('express');
var app = express.createServer(
express.logger() , express.bodyParser() );
程式碼如下:
app.use(express.logger({ format:logger({ format :method :url' })); 通常,使用connect 中間件你可能會用到require('connect'),像這樣:
var connect = require('connect'); app.use(connect.logger()); app.use(connect.bodyParser());
這在某種程度上來說有點不爽,所以express 重導出(re-exports)了這些中間件屬性,儘管他們是一樣的:
app.use(express.logger()); app.use(express.bodyParser());
中間件的順序非常重要,當Connect 收到一個請求,我們傳到createServer() 或use() 執行的第一個中間件將附帶三個參數,request、response,以及一個回調函數(通常是next)。當 next() 被調用,將輪到第二個中間件,依此類推。之所以說這是值得注意的,是因為許多中間件彼此依賴,例如methodOverride() 查詢req.body 方法來檢測HTTP 方法重載,另一方面bodyParser() 解析請求內容並將其於寄存於req. body。另一個例子是 cookie 解析和 session 支持,我們必須先 use() cookieParser() 緊接著 session()。
很多Express 應用程式都包含這樣的一行app.use(app.router),這看起來可能有點奇怪,其實它只是一個包含所有定義路由規則,並執行基於現有URL 請求和HTTP 方法路由查找的一個中間件功能。 Express 允許你決定其位置(to position),不過預設情況下它被放置於底部。透過改變路由的位置,我們可以改變中間件的優先級,譬如我們想把錯誤報告做為最後的中間件,以便任何傳給next() 的異常都可以透過它來處理;又或者我們希望靜態文件服務優先順序更低,以允許我們的路由可以監聽單一靜態檔案請求的下載次數,等等。這看起來差不多是這樣的:
app.use( express.logger(...)); app.use(express.bodyParser(...)); app.use(express.cookieParser(...)); app.use (express.session(...)); app.use(app.router); app.use(express.static(...)); app.use(express.errorHandler (...));
首先我們加入logger(),它可能包含node 的req.end() 方法,提供我們回應時間的資料。接下來請求的內容將會被解析(如果有資料的話),緊接著的是cookie 解析和session 支持,同時req.session 將會在觸發app.router 中的路由時被定義,這時我們並不調用next(),因此static() 中間件將不會知道這個請求,如若已經定義瞭如下一個路由,我們則可以記錄各種狀態、拒絕下載和消耗下載點數等。
var downloads = {};
var downloads = {};
;
; ; ; ; ; ;
;;;; ;
;;
;; ;
next();});
十、路由中間件
路由可以利用路由器中間件,傳遞一個以上的回呼函數(或是陣列)到其方法中。這個特性非常有利於限制存取、透過路由下載數據,等等。
通常非同步資料擷取看起來可能像下例,我們使用:id 參數,嘗試載入一個使用者: 程式碼如下:app.get('/user/:id', function(req, res, next){ loadUser(req.params.id, function(err, user ){ if (err) return next(err); res.send('Viewing user ' user.name); });}); });});為保證DRY 原則和提升可讀,我們可以把這個邏輯應用在一個中間件內。如下所示,抽象這個邏輯到中間件內將允許你重複使用它,同時確保了我們路由的簡潔。 複製程式碼 程式碼如下:
function loadUser(req, res, next) {
// You would fetch your user from the db
var user = users[req.params.id];
var user = users[req.params.id];
if (user = users[req.params.id];
if (user = users[user ) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' req.params.) ' }
app.get('/user/:id', loadUser, function(req, res){ res.send('Viewing user ' req.user.name);
}); 多重路由可以,並依序應用到更深一層的邏輯,如限制一個用戶帳號的存取。下面的範例只允許經過鑑定的使用者才可以編輯他(她)的帳號。 程式碼如下:
function andRestrictToSelf(req, s,ext) > req.authenticatedUser.id == req.user.id
? next()
: next(new Error('Unauthorized'));
}
app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){ res.send('Editing user ' req.user.name); } );
時時刻刻銘記路由只是簡單的函數,如下所示,我們可以定義傳回中間件的函數以創建一個更具表現力,更靈活的方案。
function andRestrictTo(role) { req, res, next) {
req.authenticatedUser.role == role
? next()
>
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
res.send('Deleted user ' req.user.name);
});
常用的中間件「堆疊」可以透過一個陣列來傳遞(會被遞歸應用),這些中間件可以混著、匹配到任何層次(which can be mixed and matched to any degree)。
複製程式碼 程式碼如下: var a = [middle1, middlewareware2] var a = [middle1, middlewareware2] b = [middleware3, middleware4] , all = [a, b];
app.get('/foo', a, function(){});
app.get('/bar', a, function(){});
app.get('/', a, middleware3, middleware4, function(){}); app.get('/', a, b, function(){});
app .get('/', all, function(){});
對於這個實例的完整程式碼,請看route middleware example 這個倉庫。
我們可能會有多次想要「跳過」剩餘的路由中間件,繼續匹配後續的路由。做到這一點,我們只需呼叫 next() 時帶上 'route' 字串 —— next('route')。如果沒有剩餘的路由符合到請求的 URL,Express 將會傳回 404 Not Found。
十一、HTTP 方法
至此已接觸了好幾次 app.get(),除此這外 Express 還提供了其他常見的 HTTP 動作,如 app.post() 、app.del() 等等。
POST 用法的一個常用例子是提交一個表單。下面我們簡單地在 html 中把表單的 method 屬性設為 post,控制權將會指派給它下面所定義的路由。
預設上Express 並不知道如何處理這個要求的內容,因此我們必須新增bodyParser 中介軟體,它將解析application/x-www -form-urlencoded 和application/json 請求的內容,並將變數存放在req.body 中。我們可以像下述示例一樣來使用這個中間件:
複製代碼
代碼如下: app.use(express.bodyParser());
如下,我們的路由將有權存取 req.body.user 對象,當有 name 和 email 被定義時它將包含這兩個屬性(譯註:如果表單發送的內容不為空的話)。
app.post('/'unction(req. res){ console.log(req.body.user); res.redirect('back'); });
當想要在一個表單中使用像PUT這樣的方法,我們可以使用一個命名為_method 的hidden input,它可以用以修改HTTP 方法。為了做這個,我們首先需要 methodOverride 中間件,它必須出現於 bodyParser 後面,以便使用它的 req.body中包含的表單值。
app.use(express.bodyParser(>
app.use(express.bodyParser(> app.use(express.bodyParser()); 🎜>app.use(express.methodOverride());
對於這些方法為何不是預設擁有,簡單來說只是因為它並不是Express 所要求完整功能所必須。方法的使用依賴於你的應用,你可能並不需要它們,客戶端仍然可以使用像 PUT 和 DELETE 這樣的方法,你可以直接使用它們,因為 methodOverride 為 form 提供了一個非常好的解決方案。以下將示範如何使用PUT 這個方法,看起來可能像: 複製程式碼
程式碼如下:
app.put('/', function(){
console.log(req.body.user); res.redirect('back');});
十二、錯誤處理
Express 提供了 app.error() 方法以便接收到的異常在一個路由裡拋出,或傳到 next(err) 中。下面這個範例將基於特定的NotFound 異常處理不同的頁面: 複製程式碼
程式碼如下:
程式碼如下:
function NotFound(msg){
this.name = 'NotFound'; Error.call(this, mments); Error.captureStackTrace(this, arguments.callee); 🎜>
NotFound.prototype.__proto__ = Error.prototype;
app.get('/404', function(req, res){ throw new NotFound;
});
app.get('/500', function(req, res){
複製程式碼 程式碼如下: app.error(function(, req, reqer, req next){ if (err instanceof NotFound) {
res.render('404.jade'); 🎜>}) ;為求簡潔(for the simplicity),這裡我們假定這個demo 的所有錯誤為500,當然你可以可以選擇自己喜歡的。像node 執行檔案系統的系統呼叫時,你可能會接收到一個帶有ENOENT 的error.code,意思是「不存在這樣的檔案或目錄」 的錯誤,我們可以在錯誤處理器中使用,或者當有需要時可顯示一個指定的頁面。
複製程式碼
程式碼如下: app.error(function(err, req, res) { res.render('500.jade', { error: err
});}); 我們的 app 同樣可以利用 Connect 的 errorHandler 中間件來報告異常。譬如當我們希望在「開發」 環境輸出stderr 異常時,我們可以使用:
代碼如下:
複製程式碼
程式碼如下:app.use(express.errorHandler(true , dumpExceptions: true }));
errorHandler 中間件還可以在Accept: application/json 存在的時候回傳json,這對於開發重度依賴客戶端Javascript 的應用非常有用。
複製程式碼 程式碼如下: app.get('/user/:userId', function(req, res, next){
User.get(req.params.userId, function(err, user){ if (err) return next(err); res.send('user ' user.name); });
}); }); }); 透過預處理,我們的參數可以映射到執行驗證、控制(coercion),甚至從資料庫載入資料的回呼。如下我們帶著參數名稱呼叫 app.param() 希望將其對應於某些中間件。如你所見,我們接受代表佔位符值的 id 參數。使用這個,我們如常載入使用者並處理錯誤,並且簡單地呼叫 next() 來把控制權交由下一個預處理或路由處理器。
複製程式碼 程式碼如下:
app.param('userId', function(req, function(req, function(req, function(req, function(req, function) res, next, id){ User.get(id, function(err, user){ if (err) return next(err); if (!user) return next(new Error 'failed to find user'));
req.user = user; next(); }); }); });
});
一旦這樣做,上述所述將會大幅提昇路由的可讀性,並且允許我們輕鬆地在整個程序中共享邏輯:
複製代碼 代碼如下:
app.get('/user/:userId', function(req, res){ res.send('user ' req.user.name); });
View 檔案件使用 . 這樣的格式,其中 是被 require 進來模組的名稱。例如 layout.ejs 將告訴 view 系統去 require('ejs'),被載入的模組必須(導出) exports.compile(str, options) 方法,並傳回一個 Function 來適應 Express。 app.register() 可用以改變這種預設行為,將檔案副檔名對應到特定的引擎。譬如 “foo.html” 可以由 ejs 來處理。
下面這個範例使用 Jade 來處理 index.html。因為我們並未使用 layout: false,index.jade 處理後的內容將會被傳入到 layout.jade 中一個名為 body 的本地變數。
複製程式碼
程式碼如下: app.get('/', function(req, function(req, res){ res.render('index.jade', { title: 'My Site' });
});
新的view engine 設定允許我們指定預設的模板引擎,例如當我們使用jade 時可以這樣設定:
程式碼如下:app .set('view engine', 'jade');允許我們這樣處理:複製程式碼複製程式碼複製程式碼複製程式碼複製程式碼複製程式碼程式碼如下:
res.render('index');
對應於: 複製程式碼
代碼如下:
複製程式碼 程式碼如下:
res.render('another-page. ejs');
Express 同時也提供了view options 設置,這將應用於一個view 每次被渲染的時候,譬如你不希望使用layouts 的時候可能會這樣做: 複製程式碼
程式碼如下:
app.set('view options', {複製程式碼
複製程式碼
res.render('myview.ejs', { layout: true });
當有需要變更一個layout,我們通常需要再指定一個路徑。譬如當我們已經把view engine 設定為jade,並且這個檔案命名為./views/mylayout.jade,我們可以這樣簡單地進行傳參:
res.render('page', { layout: 'mylayout' });
否則(譯:沒有把view engine 設定為jade 或其他的引擎時),我們必須指定一個副檔名:
res.render('page', { layout: 'mylayout.jade' }); 它們同樣可以是絕對路徑:
複製程式碼
程式碼如下:
複製程式碼
程式碼如下:app.set('view options', {
open: '{{', close: ' })
十五、View 零件
Express 的 view 系統內建了部件(partials) 和集合器(collections)的支持,相當於用一個 “迷你” 的 view 替換一個文檔碎片(document fragment)。範例,在一個view 中重複渲染來顯示評論,我們可以使用部件集:
複製程式碼 程式碼如下: partial('comment', { collection: comments });如果並不需要其他選項或本地變量,我們可以省略整個對象,簡單地傳入一個數組,這與以上是等價的:複製程式碼 程式碼如下:partial('comment' comments); 在使用中,部件集無償地提供了一些 “神奇” 本地變量的支持:
1.firstInCollection true,當它是第一個物件的時候 2.indexInCollection 在集合器物件中的索引 3.lastInCollection true,當它是最後一個物件的時候 4 .collectionLength 集合器物件的長度
本地變數的傳遞(生成)具備較高的優先權,同時,傳到父級 view 的本地變數對於子級 view 同樣適應。例如當我們用 partial('blog/post', post) 來渲染一個部落格文章,它將會產生一個 post 本地變量,在呼叫這個函數的 view 中存在本地變數 user,它將同樣對 blog/post 有效。 (譯註:這裡 partial 比較像 php 中的 include 方法)。
注意: 請謹慎使用部件集合器,渲染一個長度為 100 的部件集合數組相當於我們需要處理 100 個 view。對於簡單的集合,最好重複內置,而非使用部件集合器以避免開銷過大。
十六、View 查找
View 尋找相對於父級view (路徑)執行,如我們有一個view 頁面叫作views/user/list.jade,並且在其內部寫有partial('edit') 則它會嘗試載入views/ user/edit.jade,同理partial('../messages') 將會載入views/messages.jade。
View 系統也支援範本索引,讓你可以使用一個與 view 同名的目錄。例如在一個路由中,res.render('users') 得到的非 views/users.jade 即 views/users/index.jade。 (譯註:先處理 . 的情況,再處理 / 的情況,詳情可見 view.js。)
當使用上述view 索引,我們在與view 同一個目錄下,使用partial('users') 中引用views/users/index.jade,與此同時view 系統會嘗試索引../users/index,而無須我們呼叫partial('users')。
十七、Template Engines
下列為 Express 最常用的模板引擎:
1.Haml:haml 實作 2.Jade:haml.js 繼位者 3.EJS:嵌入式JavaScript 4.CoffeeKup:基於CoffeeScript 的模板 5.jQuery Templates
十八、Session 支援
Session 支援可以透過使用 Connect 的 session 中間件來取得,為此通常我們同時需要在其前面加上 cookieParser 中介軟體,它將解析並儲存 cookie 資料於 req.cookies 中。
app.use(express.cookieParser()); 🎜>app.use(express.session({ secret: "keyboard cat" }));
預設情況下session 中間件使用Connect 內建的記憶體存儲,然而還有其他多種實作方式。如connect-redis 提供了一種Redis 的session 存儲,它這可像下面這樣被使用:
var RedisStore = require('connect-redis')(express); app.use(express.cookieParser()); app.use(express.session( { secret: "keyboard cat", store: new RedisStore }));
至此,req.session 和req.sessionStore 屬性將可以被所有路由和後繼的中間件使用。在req.session 上的所有屬性都會在一個回應中自動保存下來,譬如當我們想要添加資料到購物車:
var RedisStore = require('connect-redis')(express);
app.use(express.bodyParser());
app .use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
app.post('/add-to-cart', function(req, res){
// 我們可能透過一個表單POST 出多個item // (在些使用bodyParser()中間件) var items = req.body.items; req.session.items = items; res.redirect('back'); });
app.get('/add-to-cart', function(req, res){
// 當返回時,頁面GET /add-to-cart // 我們可以檢查req. session.items && req.session.items.length // 來印出提示 if (req.session.items && req.session.items.length) { req.session.items.length) { , 'You have %s items in your cart', req.session.items.length); } res.render('shopping-cart'); });
對於 req.session 對旬,它還有像 Session#touch()、Session#destroy()、 Session#regenerate() 等用以維護和操作 session 的方法。更多的詳情請看 Connect Session 的文件。
十九、升級指南
對於使用Express 1.x 的同學,如果你有很重要的程序需要升級到2.x 以獲得更好的支持,請看官方非常詳細的遷移指南: http:/ /expressjs.com/guide.html#migration-guide