Express.js 簡介:參數、錯誤處理和其他中間件
注意: 本文是 Express.js 指南的一部分:Express.js 綜合書籍。
Express.js 是最流行和最成熟的 Node.js 框架之一。您可以在 Express.js 簡介 中了解更多信息 webapplog.com 上的系列:
- Express.js 簡介:使用 Monk 和 MongoDB 的簡單 REST API 應用
- Node.js MVC:Express.js + Derby Hello World 教程
要了解如何從頭開始創建應用程序,請參閱之前的帖子。
請求處理程序
Express.js 是一個 node.js 框架,它提供了一種組織路由的方法。每個路由都是通過對應用程序對象的方法調用定義的,其中 URL 模式作為第一個參數(也支持正則表達式),例如:
app.get('api/v1/stories/', function(res, req){
...
})
或者,對於 POST 方法:
app.post('/api/v1/stories'function(req,res){
...
})
不用說也支持 DELETE 和 PUT 方法。
我們傳遞給 get()
的回調 或 post()
方法稱為請求處理程序,因為它們接受請求(req
),處理它們並寫入響應 (res
) 對象。例如:
app.get('/about', function(req,res){
res.send('About Us: ...');
});
我們可以有多個請求處理程序,因此名稱 middleware .他們接受第三個參數 next
調用 which (next()
) 將執行流程切換到下一個處理程序:
app.get('/api/v1/stories/:id', function(req,res, next) {
//do authorization
//if not authorized or there is an error
// return next(error);
//if authorized and no errors
return next();
}), function(req,res, next) {
//extract id and fetch the object from the database
//assuming no errors, save story in the request object
req.story = story;
return next();
}), function(req,res) {
//output the result of the database search
res.send(res.story);
});
URL模式中故事的ID是一個查詢字符串參數,我們需要在數據庫中找到匹配的項目。
參數中間件
參數是在請求的 URL 的查詢字符串中傳遞的值。如果我們沒有 Express.js 或類似的庫,而只能使用核心 Node.js 模塊,我們就必須通過一些 require('querystring').parse(url)
從 HTTP.request 對像中提取參數 或 require('url').parse(url, true)
功能詭計。
感謝 Connect 框架和 VisionMedia 的人員,Express.js 已經以中間件的形式支持參數、錯誤處理和許多其他重要功能。這就是我們如何在我們的應用程序中插入參數中間件:
app.param('id', function(req,res, next, id){
//do something with id
//store id or other info in req object
//call next when done
next();
});
app.get('/api/v1/stories/:id',function(req,res){
//param middleware will be execute before and
//we expect req object already have needed info
//output something
res.send(data);
});
例如:
app.param('id', function(req,res, next, id){
req.db.get('stories').findOne({_id:id}, function (e, story){
if (e) return next(e);
if (!story) return next(new Error('Nothing is found'));
req.story = story;
next();
});
});
app.get('/api/v1/stories/:id',function(req,res){
res.send(req.story);
});
或者我們可以使用多個請求處理程序,但概念保持不變:我們可以期望有 req.story
對像或在執行此代碼之前拋出的錯誤,因此我們抽象了獲取參數及其各自對象的通用代碼/邏輯:
app.get('/api/v1/stories/:id', function(req,res, next) {
//do authorization
}),
//we have an object in req.story so no work is needed here
function(req,res) {
//output the result of the database search
res.send(story);
});
授權和輸入衛生也是駐留在中間件中的不錯選擇。
函數param()
特別酷,因為我們可以組合不同的鍵,例如:
app.get('/api/v1/stories/:storyId/elements/:elementId',function(req,res){
res.send(req.element);
});
錯誤處理
錯誤處理通常在整個應用程序中使用,因此最好將其實現為中間件。它有相同的參數加上一個,error
:
[旁注]
閱讀博客文章很好,但觀看視頻課程更好,因為它們更具吸引力。
許多開發人員抱怨 Node.js 上缺乏負擔得起的高質量視頻材料。觀看 YouTube 視頻會讓人分心,花 500 美元購買 Node 視頻課程很瘋狂!
去看看 Node University,它有關於 Node 的免費視頻課程:node.university。
[旁注結束]
app.use(function(err, req, res, next) {
//do logging and user-friendly error message display
res.send(500);
})
其實響應可以是任何東西:
JSON 字符串
app.use(function(err, req, res, next) {
//do logging and user-friendly error message display
res.send(500, {status:500, message: 'internal error', type:'internal'});
})
短信
app.use(function(err, req, res, next) {
//do logging and user-friendly error message display
res.send(500, 'internal server error');
})
錯誤頁面
app.use(function(err, req, res, next) {
//do logging and user-friendly error message display
//assuming that template engine is plugged in
res.render('500');
})
重定向到錯誤頁面
app.use(function(err, req, res, next) {
//do logging and user-friendly error message display
res.redirect('/public/500.html');
})
錯誤 HTTP 響應狀態(401、400、500 等)
app.use(function(err, req, res, next) {
//do logging and user-friendly error message display
res.end(500);
})
順便說一句,日誌記錄也應該抽像在一個中間件中!
要從請求處理程序和中間件中觸發錯誤,您只需調用:
next(error);
或
next(new Error('Something went wrong :-(');
您還可以擁有多個錯誤處理程序,並使用命名函數而不是匿名函數,如 Express.js 錯誤處理指南中所示。
其他中間件
除了提取參數之外,它還可以用於許多事情,例如授權、錯誤處理、會話、輸出等。
res.json()
是其中之一。它方便地將 JavaScript/Node.js 對象輸出為 JSON。例如:
app.get('/api/v1/stories/:id', function(req,res){
res.json(req.story);
});
相當於(如果 req.story
是一個數組和對象):
app.get('/api/v1/stories/:id', function(req,res){
res.send(req.story);
});
或
app.get('api/v1/stories/:id',function(req,res){
res.set({
'Content-Type': 'application/json'
});
res.send(req.story);
});
抽象
中間件是靈活的。您可以使用匿名或命名函數,但最好的辦法是根據功能將請求處理程序抽像到外部模塊中:
var stories = require.('./routes/stories');
var elements = require.('./routes/elements');
var users = require.('./routes/users');
...
app.get('/stories/,stories.find);
app.get('/stories/:storyId/elements/:elementId', elements.find);
app.put('/users/:userId',users.update);
路線/故事.js:
module.exports.find = function(req,res, next) {
};
路線/元素.js:
module.exports.find = function(req,res,next){
};
路線/users.js:
module.exports.update = function(req,res,next){
};
你可以使用一些函數式編程技巧,像這樣:
function requiredParamHandler(param){
//do something with a param, e.g., check that it's present in a query string
return function (req,res, next) {
//use param, e.g., if token is valid proceed with next();
next();
});
}
app.get('/api/v1/stories/:id', requiredParamHandler('token'), story.show);
var story = {
show: function (req, res, next) {
//do some logic, e.g., restrict fields to output
return res.send();
}
}
如您所見,中間件是保持代碼組織的強大概念。最佳實踐是通過將所有邏輯移動到相應的外部模塊/文件中來保持路由器精簡。這樣,重要的服務器配置參數將整齊地放在一個地方,當您需要它們時就在那兒! :-)