Express 曾是使用 Node.js 開發 Web 應用程序最流行的框架。然而,近年來該框架的活躍開發有所減少,導致其缺乏對現代 JavaScript 特性的支持。與此同時,許多采用不同 Node.js 應用程序開發方法的新框架湧現,Fastify 就是其中之一。
本文將探討 Fastify 成為 Node.js Web 應用程序開發中引人注目的替代方案的原因。我們將學習如何避免從頭重寫現有的 Express 應用程序,而是逐步遷移到 Fastify。學習完本文後,您將能夠自信地遷移現有的 Express 應用程序,並開始利用 Fastify 框架的優勢。
閱讀本文需要滿足以下條件:
本文中的所有示例代碼都可以在 GitHub 上找到,您可以瀏覽、下載和試驗。
我的網站上也提供本文的視頻版本。
fastify-express
插件通過允許在 Fastify 框架中使用 Express 中間件和路由,從而促進了從 Express 到 Fastify 的逐步遷移。 如果您熟悉使用 Express 構建 Node.js 應用程序,您可能想知道將現有的 Express 應用程序遷移到 Fastify 的好處是什麼。以下是考慮遷移的一些重要原因:
開箱即用的驗證和日誌記錄。這些功能在構建 Web 應用程序時通常是必需的。使用 Fastify 時,無需選擇和集成這些任務的庫,因為它為我們提供了這些功能。我們將在本文後面詳細了解這些功能。
對異步代碼的原生支持。 Fastify 原生處理 Promise 並支持 async/await。這意味著路由將為我們捕獲未捕獲的已拒絕 Promise。這允許我們安全地編寫異步代碼。它還允許我們做一些簡潔的事情,例如自動將路由處理程序函數的返回值作為響應正文發送:
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
自動解析和序列化 JSON。我們不需要配置 Fastify 來解析 JSON 請求正文,也不需要將對象序列化為 JSON 以進行響應。它會自動為我們處理所有這些:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
開發者友好。憑藉明確且表達力強的 API,以及對 TypeScript 的出色支持,Fastify 的設計考慮到了開發者體驗。
速度快。我們不希望框架成為應用程序中性能瓶頸的來源。好消息是 Fastify 的構建旨在實現高性能。 Fastify 基準測試顯示了它與其他 Node.js Web 框架相比的情況。
積極開發中。 Fastify 框架正在積極開發中。定期發布改進和錯誤/安全修復。
我們希望在應用程序遷移到 Fastify 後,確信它仍然按預期工作。有助於我們發現錯誤或識別意外更改的一件事是 API 集成測試。
集成測試以與單元測試不同的方式來測試應用程序的組件。單元測試單獨測試各個組件的功能。集成測試允許我們驗證多個組件協同工作的行為。
如果我們為 Express 應用程序編寫 API 集成測試,我們希望能夠在將應用程序遷移到 Fastify 後運行相同的測試。在為 API 編寫集成測試時,需要考慮以下幾點:
我們不會在本文中詳細介紹如何實現 API 集成測試,但您應該在進行框架遷移之前考慮編寫它們。
將現有的 Express 應用程序遷移到完全不同的框架的想法可能看起來相當令人生畏。幸運的是,Fastify 團隊創建了一個插件——fastify-express——可以幫助簡化遷移路徑。
fastify-express 插件為 Fastify 添加了完全的 Express 兼容性。它提供了一個 use() 方法,我們可以使用它將 Express 中間件和路由添加到我們的 Fastify 服務器。這使我們可以選擇逐步將現有 Express 應用程序的部分遷移到 Fastify。
這是一個 Express 路由器的示例:
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
然後,我們可以使用 fastify-express 將我們現有的 Express 路由器添加到 Fastify 服務器實例:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
稍後,當我們開始將應用程序遷移到 Fastify 時,我們將探討所有這些工作原理的細節。
重要的是要注意,使用 fastify-express 插件不是長期的解決方案。如果我們想要獲得 Fastify 的全部好處,我們最終需要遷移我們的 Express 特定應用程序代碼。但是,fastify-express 插件為我們提供了分階段遷移到 Fastify 的機會。
我們將構建一個示例 Express 應用程序,然後將其遷移到使用 Fastify 框架。現在讓我們來看一下它的代碼。
首先,讓我們創建一個新項目:
<code>// src/routes.js const router = express.Router(); router.get("/:user_id", function getUser(request, response, next) { response.json({}); }); export default router; </code>
然後,我們將在終端中運行此命令來安裝我們的 Express 應用程序所需的依賴項:
<code>// src/server.js import Fastify from "fastify"; import ExpressPlugin from "fastify-express"; import routes from "./routes.js"; const fastify = Fastify(); await fastify.register(ExpressPlugin); fastify.use("/user", routes); await fastify.listen(3000); </code>
最後,打開 package.json 並將以下行添加到 scripts 部分上方:
<code>mkdir express-to-fastify-migration cd express-to-fastify-migration npm init -y </code>
這將允許我們在我們的應用程序中加載 ES 模塊。
我們將創建一個 Express 路由器實例來幫助我們封裝我們的路由和中間件。 Express 中的路由器可以用來幫助我們將應用程序組織成離散的模塊。例如,我們可能有一個用於 /user 路由的路由器,另一個用於 /address 路由的路由器。我們稍後將看到這如何幫助我們分階段將 Express 應用程序遷移到 Fastify。
讓我們創建一個路由器實例並向其添加一些中間件:
<code>npm install express cors </code>
在上面的代碼中,我們配置了兩個 Express 中間件示例:
這些中間件工具將針對我們在此路由器上定義的任何路由發出的任何請求運行。
現在我們已經配置了中間件,我們可以將第一個路由添加到我們的路由器:
<code>"type": "module", </code>
在一個真實的應用程序中,上面的路由處理程序函數將驗證它接收到的數據,然後調用數據庫以創建一個新的用戶記錄。對於此示例,我們正在發送作為響應正文接收到的數據。
現在我們將添加一個用於檢索用戶的路由:
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
與 POST 路由一樣,上面的路由處理程序通常會調用數據庫來檢索用戶數據,但對於此示例,我們已硬編碼一個對像以在響應正文中發送。
最後,我們將導出路由器對象,以便我們可以在另一個模塊中導入它:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
現在我們將創建一個 app 模塊:
<code>// src/routes.js const router = express.Router(); router.get("/:user_id", function getUser(request, response, next) { response.json({}); }); export default router; </code>
在此模塊中,我們定義了一個函數,該函數創建一個新的 Express 服務器實例。然後,我們將路由器對象添加到服務器實例。
最後,我們將創建一個服務器模塊。此模塊使用我們在 app 模塊中定義的 buildApp() 函數來創建一個新的 Express 服務器實例。然後,它通過將其配置為偵聽端口 3000 來啟動我們的 Express 服務器:
<code>// src/server.js import Fastify from "fastify"; import ExpressPlugin from "fastify-express"; import routes from "./routes.js"; const fastify = Fastify(); await fastify.register(ExpressPlugin); fastify.use("/user", routes); await fastify.listen(3000); </code>
我們現在有一個完整的可運行的 Express 應用程序,我們可以在終端中運行它:
<code>mkdir express-to-fastify-migration cd express-to-fastify-migration npm init -y </code>
在另一個終端中,我們可以使用 cURL 向 API 發出請求以確認它是否正在工作:
<code>npm install express cors </code>
我們應該收到一個如下所示的響應:
<code>"type": "module", </code>
現在我們已經擁有了一個功能齊全的 Express 應用程序,我們將將其遷移到使用 Fastify 框架。
我們需要安裝三個依賴項:
讓我們在終端中運行此命令來安裝它們:
<code>// src/routes.js import express from "express"; import cors from "cors"; const router = express.Router(); router.use(express.json()); router.use(cors({ origin: true })); </code>
您可以在 GitHub 上查看這些代碼更改的差異。
現在我們已經安裝了依賴項,我們需要重構我們的 app 模塊。我們將將其更改為:
這是我們進行這些更改後的樣子:
<code>// src/routes.js router.post("/", function createUser(request, response, next) { const newUser = request.body; if (!newUser) { return next(new Error("Error creating user")); } response.status(201).json(newUser); }); </code>
您可以在 GitHub 上查看這些代碼更改的差異。
您會在上面的代碼中註意到,當我們創建 Fastify 服務器實例時,我們正在傳遞 logger 選項。這啟用了 Fastify 的內置日誌記錄功能。我們稍後將詳細了解這一點。
現在我們需要更改我們的服務器模塊以與 Fastify 服務器實例一起工作:
<code>// src/routes.js router.get("/:user_id", function getUser(request, response, next) { const user = { id: request.params.user_id, first_name: "Bobinsky", last_name: "Oso", }; response.json(user); }); </code>
您可以在 GitHub 上查看這些代碼更改的差異。
由於 Fastify 原生支持 Promise,因此在上面的代碼中,我們可以使用 await,然後使用 Fastify 的內置日誌記錄功能捕獲並記錄任何錯誤。
我們的應用程序現在使用 Fastify 來路由請求和發送響應。它功能齊全,但我們的路由仍在使用 Express。為了完全遷移到 Express,我們需要將我們的路由遷移到也使用 Fastify。
Express 應用程序中的路由封裝在 Express 路由器中。我們將此路由器重構為 Fastify 插件。插件是 Fastify 的一項功能,允許我們封裝路由和任何相關功能。
我們將從刪除一些 Express 特定的行開始重構我們的路由模塊 (src/routes.js):
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
然後我們需要將默認模塊導出更改為接受 Fastify 服務器實例的異步函數。這是 Fastify 插件的基礎。我們的路由模塊中的其餘代碼將移動到此插件函數中:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
為了使我們的中間件和路由與 Fastify 一起工作,我們需要更改:
進行所有這些更改後,我們的路由模塊現在是一個包含 Fastify 路由的 Fastify 插件:
<code>// src/routes.js const router = express.Router(); router.get("/:user_id", function getUser(request, response, next) { response.json({}); }); export default router; </code>
現在我們需要更改我們的 app 模塊 (src/app.js) 以使用我們從路由模塊導出的插件。這意味著用對 fastify.register() 的調用替換 fastify.use() 調用:
<code>// src/server.js import Fastify from "fastify"; import ExpressPlugin from "fastify-express"; import routes from "./routes.js"; const fastify = Fastify(); await fastify.register(ExpressPlugin); fastify.use("/user", routes); await fastify.listen(3000); </code>
您可以在 GitHub 上查看這些代碼更改的差異。
我們的示例 Express 應用程序只有一個路由器,因此我們可以一次性將應用程序中的所有路由遷移到使用 Fastify。但是,如果我們有一個更大的 Express 應用程序,其中有多個路由器,我們可以一次逐步將每個路由器遷移到 Fastify。
我們的應用程序狀況良好,我們幾乎已經完全將其從 Express 遷移到 Fastify。還有一件事需要遷移:我們對 cors Express 中間件包的使用。我們之前安裝了 fastify-cors 插件,現在我們需要在我們的應用程序中添加它以替換 cors 中間件。
在我們的路由模塊 (src/routes.js) 中,我們需要替換 cors 中間件的導入:
<code>mkdir express-to-fastify-migration cd express-to-fastify-migration npm init -y </code>
然後我們需要用對 fastify.register() 的調用替換對 fastify.use() 的調用:
<code>npm install express cors </code>
請注意,當我們將插件與 Fastify 註冊時,我們需要將插件函數和選項對像作為單獨的參數傳遞。
由於我們不再使用 fastify-express 插件提供的 use() 函數,因此我們可以將其完全從我們的應用程序中刪除。為此,讓我們從我們的 app 模塊 (src/app.js) 中刪除以下幾行:
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
您可以在 GitHub 上查看這些代碼更改的差異。
我們的應用程序從 Express 到 Fastify 的遷移已完成!我們現在可以通過在終端中運行此命令來刪除 Express 相關的依賴項:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
您可以在 GitHub 上查看這些代碼更改的差異。
現在我們已經將應用程序完全遷移到 Fastify,現在是檢查一切是否仍按預期工作的好時機。讓我們運行我們之前在應用程序使用 Express 時運行的相同命令。
首先,我們將在終端中運行應用程序:
<code>// src/routes.js const router = express.Router(); router.get("/:user_id", function getUser(request, response, next) { response.json({}); }); export default router; </code>
然後,在另一個終端中,我們將使用 cURL 向 API 發出請求以確認它是否按預期工作:
<code>// src/server.js import Fastify from "fastify"; import ExpressPlugin from "fastify-express"; import routes from "./routes.js"; const fastify = Fastify(); await fastify.register(ExpressPlugin); fastify.use("/user", routes); await fastify.listen(3000); </code>
我們應該收到一個如下所示的響應:
<code>mkdir express-to-fastify-migration cd express-to-fastify-migration npm init -y </code>
我們的示例 Express 應用程序只使用了一些中間件函數,但我們現實世界的 Express 應用程序可能使用了更多。正如我們所看到的,fastify-express 插件允許我們繼續使用 Express 中間件(如果需要)。這使我們可以推遲將我們自己的自定義 Express 中間件重寫為 Fastify 插件。但是,我們如何替換第三方 Express 中間件呢?
幸運的是,Fastify 提供了一個健康的插件生態系統。以下是一些我們可以用 Fastify 插件替換的流行 Express 中間件包:
一些 Fastify 插件是其 Express 對應項的直接移植或包裝器。這意味著我們通常不需要更改傳遞給 Fastify 插件的配置選項。
您可以在 Fastify 生態系統頁面上找到完整的插件列表。
現在我們已經開始通過遷移 Express 應用程序來熟悉 Fastify,現在是時候開始查看我們可以從中受益的其他 Fastify 功能了。
Fastify 提供了請求驗證功能。它在後台使用 Ajv(另一個 JSON 模式驗證器),這允許我們使用 JSON Schema 定義驗證規則。
這是一個使用 JSON 模式來驗證 POST 路由上請求正文的示例:
<code>npm install express cors </code>
驗證錯誤會自動格式化並作為 JSON 響應發送:
<code>"type": "module", </code>
在 Fastify 驗證和序列化文檔中了解更多信息。
Node.js 應用程序中的日誌記錄可能會對生產環境中的性能產生負面影響。這是因為序列化和將日誌數據傳輸到其他地方(例如,到 Elasticsearch)涉及許多步驟。此應用程序方面的高度優化非常重要。
日誌記錄已完全集成到 Fastify 中,這意味著我們不需要花費時間選擇和集成日誌記錄器。 Fastify 使用快速靈活的日誌記錄器:pino。它以 JSON 格式生成日誌:
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
當我們創建 Fastify 服務器實例時,我們可以啟用日誌記錄並自定義傳遞給 pino 的選項。然後,Fastify 將自動輸出如上所示的日誌消息。日誌記錄器實例在 Fastify 服務器實例 (例如 fastify.log.info("...")) 和所有請求對象 (例如 request.log.info("...")) 上可用。
在 Fastify 日誌記錄文檔中了解更多信息。
Fastify 提供了一個 setErrorHandler() 方法,允許我們明確指定錯誤處理函數。這與 Express 不同,在 Express 中,錯誤處理中間件只能通過它接受的參數 (err, req, res, next) 來區分,並且必須以特定的順序添加。
為了獲得完全的靈活性,我們可以在不同的插件中指定不同的 Fastify 錯誤處理程序。在 Fastify 錯誤文檔中了解更多信息。
裝飾器是 Fastify 中的一項強大功能,允許我們自定義核心 Fastify 對象——例如我們的 Fastify 服務器實例——以及請求和回復對象。這是一個基本裝飾器的示例:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
裝飾器允許我們將數據庫連接或視圖引擎等內容在整個 Fastify 應用程序中使用。在 Fastify 裝飾器文檔中了解更多信息。
在本文中,我們學習瞭如何將現有的 Node.js 應用程序從 Express 遷移到 Fastify。我們已經了解了 fastify-express 插件如何幫助我們逐步遷移現有應用程序。這使我們可以開始受益於 Fastify 提供的功能,即使我們的應用程序的部分仍在使用 Express。
以下是一些您在從 Express 遷移到 Fastify 時可能會發現有用的資源:
Express 和 Fastify 都是 Node.js 的 Web 框架,但它們有一些關鍵區別。 Express 是一個極簡的 Web 應用程序框架,提供了一個簡單的界面來構建 Web 應用程序和 API。它已經存在很長時間了,擁有龐大的社區和豐富的中間件。另一方面,Fastify 是一個較新的框架,專注於以最少的開銷和強大的插件架構提供最佳的開發者體驗。它的設計速度很快,因此得名,基準測試表明它每秒可以處理比 Express 更多的請求。
從 Express 遷移到 Fastify 包括幾個步驟。首先,您需要安裝 Fastify 並將應用程序中的 Express 實例替換為 Fastify 實例。然後,您需要將 Express 特定的中間件替換為 Fastify 插件或自定義代碼。您還需要更新您的路由以使用 Fastify 的路由系統。最後,您需要更新您的錯誤處理代碼以使用 Fastify 的錯誤處理機制。
Fastify 有自己的中間件系統,但它也通過“middie”插件支持 Express 風格的中間件。但是,在 Fastify 中使用 Express 中間件可能會影響性能,因此建議盡可能使用 Fastify 插件或自定義代碼。
Fastify 具有內置的錯誤處理機制,您可以使用它來處理應用程序中的錯誤。您可以為特定路由或整個應用程序定義自定義錯誤處理程序。 Fastify 還支持 async/await 語法,這使得錯誤處理比 Express 更直接。
鉤子是 Fastify 中的一項強大功能,允許您在請求/響應生命週期的不同階段運行自定義代碼。您可以使用鉤子來修改請求或響應、執行身份驗證、記錄請求等等。 Fastify 支持多個鉤子,包括“onRequest”、“preHandler”、“onSend”和“onResponse”。
插件是 Fastify 的一個關鍵功能,允許您擴展應用程序的功能。您可以使用插件來添加新功能、與其他服務集成或封裝應用程序邏輯。 Fastify 擁有豐富的插件生態系統,您也可以創建自己的插件。
Fastify 擁有一個強大的路由系統,支持參數、查詢字符串、通配符等等。您可以使用“route”方法定義路由,該方法接受一個選項對象,該對象指定路由的方法、URL、處理程序和其他選項。
在 Fastify 中,您可以使用傳遞給路由處理程序的“reply”對象發送響應。 “reply”對像有幾種方法可以發送響應,包括“send”、“code”、“header”和“type”。 Fastify 還自動序列化 JSON 響應以提高性能。
Fastify 使用 JSON Schema 支持請求和響應驗證。您可以為路由定義模式,Fastify 將自動根據這些模式驗證傳入的請求和傳出的響應。這有助於儘早發現錯誤並提高應用程序的可靠性。
Fastify 的設計目的是快速高效。它使用輕量級架構,支持 HTTP/2 和 HTTP/3,並擁有強大的插件系統,可最大限度地減少開銷。 Fastify 還自動序列化 JSON 響應並支持請求和響應驗證,這有助於提高性能。
以上是如何將您的應用程序從Express遷移到快速的詳細內容。更多資訊請關注PHP中文網其他相關文章!