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