Hello!感謝你來(lái)到 Fastify 的世界!這篇文檔將向你介紹 Fastify 框架及其特性,也包含了一些示例和指向其他文檔的鏈接。那,這就開(kāi)始吧!
使用 npm 安裝:
npm i fastify --save
使用 yarn 安裝:
yarn add fastify
讓我們開(kāi)始編寫(xiě)第一個(gè)服務(wù)器吧:
// 加載框架并新建實(shí)例
const fastify = require('fastify')({
logger: true
})
// 聲明路由
fastify.get('/', function (request, reply) {
reply.send({ hello: 'world' })
})
// 啟動(dòng)服務(wù)!
fastify.listen(3000, function (err, address) {
if (err) {
fastify.log.error(err)
process.exit(1)
}
fastify.log.info(`server listening on ${address}`)
})
更喜歡使用 async/await?Fastify 對(duì)其提供了開(kāi)箱即用的支持。(我們還建議使用 make-promises-safe 來(lái)避免文件描述符 (file descriptor) 及內(nèi)存的泄露)
const fastify = require('fastify')()
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
const start = async () => {
try {
await fastify.listen(3000)
} catch (err) {
fastify.log.error(err)
process.exit(1)
}
}
start()
如此簡(jiǎn)單,棒極了!可是,一個(gè)復(fù)雜的應(yīng)用需要比上例多得多的代碼。當(dāng)你從頭開(kāi)始構(gòu)建一個(gè)應(yīng)用時(shí),會(huì)遇到一些典型的問(wèn)題,如多個(gè)文件的操作、異步引導(dǎo),以及代碼結(jié)構(gòu)的布置。幸運(yùn)的是,F(xiàn)astify 提供了一個(gè)易于使用的平臺(tái),它能幫助你解決不限于上述的諸多問(wèn)題!
注本文檔中的示例,默認(rèn)情況下只監(jiān)聽(tīng)本地 127.0.0.1 端口。要監(jiān)聽(tīng)所有有效的 IPv4 端口,需要將代碼修改為監(jiān)聽(tīng) 0.0.0.0,如下所示:fastify.listen(3000, '0.0.0.0', function (err, address) { if (err) { fastify.log.error(err) process.exit(1) } fastify.log.info(`server listening on ${address}`) })類似地,::1 表示只允許本地的 IPv6 連接。而 :: 表示允許所有 IPv6 地址的接入,當(dāng)操作系統(tǒng)支持時(shí),所有的 IPv4 地址也會(huì)被允許。當(dāng)使用 Docker 或其他容器部署時(shí),這會(huì)是最簡(jiǎn)單的暴露應(yīng)用的方式。
就如同在 JavaScript 中一切皆為對(duì)象,在 Fastify 中,一切都是插件 (plugin)。在深入之前,先來(lái)看看插件系統(tǒng)是如何工作的吧!讓我們新建一個(gè)基本的服務(wù)器,但這回我們把路由 (route) 的聲明從入口文件轉(zhuǎn)移到一個(gè)外部文件。(參閱路由聲明)。
const fastify = require('fastify')({
logger: true
})
fastify.register(require('./our-first-route'))
fastify.listen(3000, function (err, address) {
if (err) {
fastify.log.error(err)
process.exit(1)
}
fastify.log.info(`server listening on ${address}`)
})
// our-first-route.js
async function routes (fastify, options) {
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
}
module.exports = routes
這個(gè)例子調(diào)用了 register API,它是 Fastify 框架的核心,也是添加路由、插件等的唯一方法。
在本文的開(kāi)頭,我們提到 Fastify 提供了幫助應(yīng)用異步引導(dǎo)的基礎(chǔ)功能。為什么這一功能十分重要呢? 考慮一下,當(dāng)存在數(shù)據(jù)庫(kù)操作時(shí),數(shù)據(jù)庫(kù)連接顯然要在服務(wù)器接受外部請(qǐng)求之前完成。該如何解決這一問(wèn)題呢?典型的解決方案是使用復(fù)雜的回調(diào)函數(shù)或 Promise,但如此會(huì)造成框架的 API、其他庫(kù)以及應(yīng)用程序的代碼混雜在一起。Fastify 則不走尋常路,它從本質(zhì)上用最輕松的方式解決這一問(wèn)題!
讓我們重寫(xiě)上述示例,加入一個(gè)數(shù)據(jù)庫(kù)連接。(在這里我們用簡(jiǎn)單的例子來(lái)說(shuō)明,對(duì)于健壯的方案請(qǐng)考慮使用 fastify-mongo 或 Fastify 生態(tài)中的其他插件)
server.js
const fastify = require('fastify')({
logger: true
})
fastify.register(require('./our-db-connector'), {
url: 'mongodb://localhost:27017/'
})
fastify.register(require('./our-first-route'))
fastify.listen(3000, function (err, address) {
if (err) {
fastify.log.error(err)
process.exit(1)
}
fastify.log.info(`server listening on ${address}`)
})
our-db-connector.js
const fastifyPlugin = require('fastify-plugin')
const MongoClient = require('mongodb').MongoClient
async function dbConnector (fastify, options) {
const url = options.url
delete options.url
const db = await MongoClient.connect(url, options)
fastify.decorate('mongo', db)
}
// 用 fastify-plugin 包裝插件,以使插件中聲明的裝飾器、鉤子函數(shù)及中間件暴露在根作用域里。
module.exports = fastifyPlugin(dbConnector)
our-first-route.js
async function routes (fastify, options) {
const database = fastify.mongo.db('db')
const collection = database.collection('test')
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
fastify.get('/search/:id', async (request, reply) => {
const result = await collection.findOne({ id: request.params.id })
if (result.value === null) {
throw new Error('Invalid value')
}
return result.value
})
}
module.exports = routes
哇,真是快??!介紹了一些新概念后,讓我們回顧一下迄今為止都做了些什么吧。如你所見(jiàn),我們可以使用 register 來(lái)注冊(cè)數(shù)據(jù)庫(kù)連接器或者路由。 這是 Fastify 最棒的特性之一了!它使得插件按聲明的順序來(lái)加載,唯有當(dāng)前插件加載完畢后,才會(huì)加載下一個(gè)插件。如此,我們便可以在第一個(gè)插件中注冊(cè)數(shù)據(jù)庫(kù)連接器,并在第二個(gè)插件中使用它。(參見(jiàn) 這里 了解如何處理插件的作用域)。 當(dāng)調(diào)用函數(shù) fastify.listen()、fastify.inject() 或 fastify.ready() 時(shí),插件便開(kāi)始加載了。
我們還用到了 decorate API?,F(xiàn)在來(lái)看看這一 API 是什么,以及它是如何運(yùn)作的吧。 考慮下需要在應(yīng)用的不同部分使用相同的代碼或庫(kù)的場(chǎng)景。一種解決方案便是按需引入這些代碼或庫(kù)。哈,這固然可行,但卻因?yàn)橹貜?fù)的代碼和麻煩的重構(gòu)讓人苦惱。為了解決上述問(wèn)題,F(xiàn)astify 提供了 decorate API。它允許你在 Fastify 的命名空間下添加自定義對(duì)象,如此一來(lái),你就可以在所有地方直接使用這些對(duì)象了。
更深入的內(nèi)容,例如插件如何運(yùn)作、如何新建,以及使用 Fastify 全部的 API 去處理復(fù)雜的異步引導(dǎo)的細(xì)節(jié),請(qǐng)看插件指南。
為了保證應(yīng)用的行為一致且可預(yù)測(cè),我們強(qiáng)烈建議你采用以下的順序來(lái)組織代碼:
└── 來(lái)自 Fastify 生態(tài)的插件
└── 你自己的插件
└── 裝飾器
└── 鉤子函數(shù)和中間件
└── 你的服務(wù)應(yīng)用
這確保了你總能訪問(wèn)當(dāng)前作用域下聲明的所有屬性。如前文所述,F(xiàn)astify 提供了一個(gè)可靠的封裝模型,它能幫助你的應(yīng)用成為單一且獨(dú)立的服務(wù)。假如你要為某些路由單獨(dú)地注冊(cè)插件,只需復(fù)寫(xiě)上述的結(jié)構(gòu)就足夠了。
└── 來(lái)自 Fastify 生態(tài)的插件
└── 你自己的插件
└── 裝飾器
└── 鉤子函數(shù)和中間件
└── 你的服務(wù)應(yīng)用
│
└── 服務(wù) A
│ └── 來(lái)自 Fastify 生態(tài)的插件
│ └── 你自己的插件
│ └── 裝飾器
│ └── 鉤子函數(shù)和中間件
│ └── 你的服務(wù)應(yīng)用
│
└── 服務(wù) B
│ └── 來(lái)自 Fastify 生態(tài)的插件
│ └── 你自己的插件
│ └── 裝飾器
│ └── 鉤子函數(shù)和中間件
│ └── 你的服務(wù)應(yīng)用
數(shù)據(jù)的驗(yàn)證在我們的框架中是極為重要的一環(huán),也是核心的概念。Fastify 使用 JSON Schema 驗(yàn)證來(lái)訪的請(qǐng)求。 讓我們來(lái)看一個(gè)驗(yàn)證路由的例子:
const opts = {
schema: {
body: {
type: 'object',
properties: {
someKey: { type: 'string' },
someOtherKey: { type: 'number' }
}
}
}
}
fastify.post('/', opts, async (request, reply) => {
return { hello: 'world' }
})
這個(gè)例子展示了如何向路由傳遞配置選項(xiàng)。選項(xiàng)中包含了一個(gè)名為 schema 的對(duì)象,它便是我們驗(yàn)證路由所用的模式 (schema)。借由 schema,我們可以驗(yàn)證 body、querystring、params 以及 header。請(qǐng)參閱驗(yàn)證與序列化獲取更多信息。
Fastify 對(duì) JSON 提供了優(yōu)異的支持,極大地優(yōu)化了解析 JSON body 與序列化 JSON 輸出的過(guò)程。在 schema 的選項(xiàng)中設(shè)置 response 的值,能夠加快 JSON 的序列化 (沒(méi)錯(cuò),這很慢!),就像這樣:
const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
}
}
fastify.get('/', opts, async (request, reply) => {
return { hello: 'world' }
})
簡(jiǎn)單地指明 schema,序列化的過(guò)程就達(dá)到了原先 2-3 倍的速度。這么做同時(shí)也保護(hù)了潛在的敏感數(shù)據(jù)不被泄露,因?yàn)?Fastify 僅對(duì) schema 里出現(xiàn)的數(shù)據(jù)進(jìn)行序列化。 請(qǐng)參閱 驗(yàn)證與序列化獲取更多信息。
Fastify 生來(lái)十分精簡(jiǎn),也具有高可擴(kuò)展性。我們相信,一個(gè)小巧的框架足以實(shí)現(xiàn)一個(gè)優(yōu)秀的應(yīng)用。換句話說(shuō),F(xiàn)astify 并非一個(gè)面面俱到的框架,它依賴于自己驚人的生態(tài)系統(tǒng)!
Fastify 并沒(méi)有提供測(cè)試框架,但是我們推薦你在測(cè)試中使用 Fastify 的特性及結(jié)構(gòu)。更多內(nèi)容請(qǐng)看測(cè)試!
感謝 fastify-cli,它讓 Fastify 集成到了命令行之中。
首先,你得安裝 fastify-cli:
npm i fastify-cli
你還可以加入 -g 選項(xiàng)來(lái)全局安裝它。
接下來(lái),在 package.json 中添加如下行:
{
"scripts": {
"start": "fastify start server.js"
}
}
然后,創(chuàng)建你的服務(wù)器文件:
// server.js
'use strict'
module.exports = async function (fastify, opts) {
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
}
最后,啟動(dòng)你的服務(wù)器:
npm start
更多建議: