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中文网其他相关文章!