Fastify 系列教程:
- Fastify 系列教程一 (路由和日志)
- Fastify 系列教程二 (中間件、鈎子函數和裝飾器)
- Fastify 系列教程三 (驗證、序列化和生命周期)
- Fastify 系列教程四 (請求對象、響應對象和插件)
介紹
Fastify是一個高度專注於以最少開銷和強大的插件架構,為開發人員提供最佳體驗的Web框架。
它受到了 Hapi 和 Express 的啟發,是目前最快的 Node 框架之一。
Fastify 獨特的將 JSON Schema 應用到請求時的 validation 和響應時的 serialization,作者寫的 fast-json-stringify
包更是達到了 2x faster than JSON.stringify() 的神奇效果。
起步
安裝
輸入如下指令安裝 fastify :
npm i fastify --save
第一個 fastify 應用
app.js:
const fastify = require('fastify')()
// 注冊匹配到 '/' 的路由
fastify.get('/', function (request, reply) {
reply
.send({ text: 'hello fastify' })
})
// 監聽 3030 端口
fastify.listen(3030, err => {
if (err) {
console.log(err)
}
})
在命令行輸入 node app.js 啟動服務,在瀏覽器訪問 localhost:3030 就可以訪問到了:
路由
使用 fastify.route(option)
注冊一個路由:
fastify.route({
method: 'post',
url: '/home',
handler: function(request, reply){
reply.send({text: 'hello fastify'})
}
})
上述代碼將匹配到訪問 '/home' 的 post 請求。
詳細的 option 配置如下:
method:
目前只支持 'DELETE'
, 'GET'
, 'HEAD'
, 'PATCH'
, 'POST'
, 'PUT'
和 'OPTIONS'
。也可以是一個數組:
fastify.route({
method: ['POST', 'PUT'],
url: '/put_and_post',
handler: function(request, reply){
reply.send({hello: 'world'})
}
})
此時使用 POST
或者 PUT
方式訪問 /put_and_post
都能成功響應。
注意:Fastify 只支持
application/json
內容類型,如果想解析其他類型可以查看社區插件,fastify-formbody 可以解析x-www-form-urlencoded
請求類型,使用 fastify-multipart 處理form-data
類型請求。或者使用 ContentTypeParser 方法自定義內容類型解析器。
url:
路由匹配的路徑,也可以使用 path
代替。
所支持的 url 格式可以查看:https://github.com/delvedor/find-my-way#supported-path-formats
schema:
一個包含請求和響應模式的對象。
需要符合 JSON Schema 的格式。
body
: 驗證請求體, 必須是'POST'
或者'PUT'
請求。querystring
: 驗證查詢字符串。可以是一個完成的 JSON Schema 對象(符合{type: "object", properties: {...}}
)或者沒有type
和properties
屬性,而只有查詢字符串列表。params
: 驗證params
response
: 過濾並生成響應的 schema ,可以提升 10-20% 的吞吐量。headers
: 驗證請求頭。
使用 schema 是 Fastify 比其他框架都快的原因所在,如何理解這個 schema 呢?大家都知道,在 JSON 里面有 string
, number
, object
, array
, null
共五中類型,不同的類型格式是不一樣的,比如字符串兩頭需要加引號,數組兩頭需要加括號等等,如果不用 schema 指明 json 是何種模式的話,原生的 JSON.stringify()
會先判斷字段的類型后再做相應字符串化方法。而 JSON Schema 就是來定義 json 的數據格式的,通過 JSON Schema 可以達到驗證 JSON 是否符合規定的格式。而框架作者獨創的使用 JSON Schema 並手寫 stringify
(fast-json-stringify) ,達到了 2x faster than JSON.stringify() ,也是令人敬佩不已,也是正如 賀師俊 老師評論的那樣:
好了,言歸正傳,使用 schema 示例:
fastify.route({
method: 'GET',
url: '/',
schema: {
querystring: {
name: { type: 'number' }
},
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
},
handler: function (request, reply) {
console.log(request.query)
reply.send({ hello: 'world' })
}
})
當訪問 http://localhost:3030/put_and_post?name=2 時可以正常得到 {"hello": "world"}
,同時控制台也會輸入 {name: 2}
,注意,name=2
這條查詢字符串也被自動轉成了 number
類型了。
如果訪問 http://localhost:3030/put_and_post?name=true 會發生什么呢?對不起,Fastify 會返回 400
錯誤,因為 true
不是 number
類型,而是一個字符串:
{
"error": "Bad Request",
"message": [{
"keyword":"type",
"dataPath":".name",
"schemaPath":"#/properties/name/type",
"params":{"type":"number"},
"message":"should be number"
}],
"statusCode": 400
}
如果將 reply.send({hello: 'world'})
,改成 reply.send({text: 'world'})
會發生什么呢?
客戶端將不會得到 text
這個字段,因為 {text: 'world'}
不符合定義的 responese
規則,最終只會得到 {}
。
beforeHandler(request, reply, done):
在處理請求之前調用的函數,可以在這里處理用戶認證:
fastify.route({
method: 'POST',
path: '/authentication',
beforeHandler: function(request, reply, done){
if(request.body.username === 'lavyun') {
done()
} else {
reply.send({
meg: 'Authentication failed!',
code: '1'
})
}
},
handler: function(request, reply){
reply.send({
code: '0',
msg: 'Authentication success!'
})
}
})
handler(request, reply):
用來處理請求的函數。
schemaCompiler(schema):
schemaCompiler
是一個指定 schema 編譯器的方法。(用來驗證 body, params, headers, querystring)。默認的 schemaCompiler
返回一個實現 ajv
接口的編譯器。
比如使用 Joi:
const Joi = require('joi')
fastify.post('/the/url', {
schema: {
body: Joi.object().keys({
hello: Joi.string().required()
}).required()
},
schemaCompiler: schema => data => Joi.validate(data, schema)
})
fastify 支持類似 Express/Restify
的路由語法:
fastify.get(path, [options], handler)
fastify.head(path, [options], handler)
fastify.post(path, [options], handler)
fastify.put(path, [options], handler)
fastify.delete(path, [options], handler)
fastify.options(path, [options], handler)
fastify.patch(path, [options], handler)
fastify.all(path, [options], handler)
示例:
const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
}
}
fastify.get('/', opts, (req, reply) => {
reply.send({ hello: 'world' })
})
Async Await
使用 async/await
:
fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
return processed
})
可以看到,我們沒有調用 reply.send
來返回數據給用戶,而是使用 return
,當然你也可以使用 replay.send
:
fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
reply.send(processed)
})
警告:如果同時使用
return
和reply.send
,則后者將會被舍棄。
路由前綴
有時我們會維護兩套不同版本的api,一般我們都會手動的給所有的路由加上版本號前綴,例如:v1/user
。
Fastify 提供了快速而智能的方式來創建不同版本的相同的api,而無需手動更改所有路由名稱:
// server.js
const fastify = require('fastify')()
fastify.register(require('./routes/v1/users'), { prefix: '/v1' })
fastify.register(require('./routes/v2/users'), { prefix: '/v2' })
fastify.listen(3000)
// routes/v1/users.js
module.exports = function (fastify, opts, next) {
fastify.get('/user', handler_v1)
next()
}
// routes/v2/users.js
module.exports = function (fastify, opts, next) {
fastify.get('/user', handler_v2)
next()
}
因為在編譯時會自動處理前綴(這也意味着性能不會受到影響),所以Fastify不會抱怨兩個不同的路由使用相同的名稱。
現在客戶端將可以訪問以下路由:
/v1/user
/v2/user
日志
日志功能默認是關閉的,如果需要啟用,可以這樣做:
const fastify = require('fastify')({
logger: true
})
fastify.get('/', function (req, reply) {
reply.send({ hello: 'world' })
})
// 監聽 3030 端口
fastify.listen(3030, err => {
if (err) {
console.log(err)
}
})
此時,當收到請求后,會在控制台打印日志:
{
"pid":78817,
"hostname":"localhost",
"level":30,
"time":1508910898897,
"msg":"incoming request",
"reqId":1,
"req":{
"id":1,
"method":"GET",
"url":"/",
"remoteAddress":"::1",
"remotePort":57731
},
"v":1
}
{
"pid":78817,
"hostname":"localhost",
"level":30,
"time":1508910898907,
"msg":"request completed",
"reqId":1,
"res":{
"statusCode":200
},
"responseTime":8.21057403087616,
"v":1
}
Fastify 的更多使用將在接下來的博客中說明。
Tips:
訪問 https://lavyun.gitbooks.io/fastify/content/ 查看我翻譯的 Fastify 中文文檔。
訪問我的個人博客查看我的最新動態 lavyun.cn