长期以来,Express一直是使用Node.js开发Web应用程序的最受欢迎的框架。不幸的是,近年来,这个框架还没有看到很多积极的发展。这意味着它不支持现代JavaScript功能。同时,出现了许多新框架,它们对Node.js应用程序开发采用了不同的方法。这些框架之一是Fastify。
在本文中,我们将探讨Fastify在使用Node.js开发Web应用程序方面具有吸引力的替代方法。我们将学习如何避免从头开始重写现有的Express应用程序,而是分阶段将其迁移到使用Fastify。待完成后,您将可以放心地迁移现有的Express应用程序,并开始利用Fastify框架的优势。
与本文一起需要满足一些要求:
-
您需要习惯于创建基本的Express应用程序,定义路由和配置中间件。
-
您需要在终端中轻松运行命令。
-
您需要安装Node.js > = v14.13.0。这为我们提供了对ECMAScript(ES)模块的良好支持,并允许我们使用顶级await。本文中的代码示例使用ES模块语法(
import
/export
)。
从Express迁移到Fastify有什么好处?
如果您愿意使用Express构建Node.js应用程序,您可能想知道将现有Express应用程序迁移到Fastify的好处是什么。这是考虑采取此举的一些重要原因:
-
验证和开箱即用。构建Web应用程序时通常需要这些功能。使用Fastify时,无需为这些任务选择和集成库,因为它为我们提供了它们。我们将在本文后面的内容中详细了解这些功能。
-
对异步代码的本机支持。Fastify本机处理承诺并支持
async
/await
。这意味着路线将为我们捕获未捕获的被拒绝的诺言。这使我们可以安全地编写异步代码。它还使我们能够做一些整洁的事情,例如自动发送来自路由处理程序函数的返回值作为响应主体:app.get("/user/:id", async (request) => await getUser(request.params.id));
-
JSON的自动解析和序列化。我们不需要配置Fastify来解析JSON请求主体,也不需要将对象序列化为JSON进行响应。它为我们自动处理所有这些:
app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } });});
-
开发人员友好。借助显式和表达性的API以及对TypeScript的出色支持,Fastify的设计充分考虑了开发人员的经验。
-
很快。我们从不希望框架成为我们应用程序中性能瓶颈的根源。好消息是,Fastify已构建为具有高性能。Fastify基准测试显示了它与其他Node.js Web框架的比较。
-
在积极发展中。Fastify框架正在积极开发中。有常规版本,其中包含改进和错误/安全修复。
如何自信地迁移API
我们希望确信我们的应用程序在迁移到Fastify后仍能按预期运行。API集成测试可以帮助我们捕获错误或识别意外更改,其中之一是。
集成测试以与单元测试不同的方式行使应用程序的组件。单元测试自行行使各个组件的功能。集成测试使我们能够验证多个组件协同工作的行为。
如果我们为Express应用程序编写API集成测试,则我们希望在将应用程序迁移到Fastify后能够运行相同的测试。在为API编写集成测试时,需要考虑一些关键事项:
-
它们不应该被绑定到一个特定的框架上。我们希望能够在迁移之前和之后运行相同的测试,而无需更改测试或我们用于它们的任何库。
-
保持简单。集成测试至少应向API公开的端点发出请求,并验证是否返回了响应,但通常不会更多。我们可能要检查特定的HTTP状态代码或响应标头,但我们应尝试使测试尽可能简单。
-
选择您喜欢的工具。有很多不同的工具可以帮助我们创建和运行API测试,但重要的是要使用我们熟悉的工具。要编写有效的集成测试,我们需要能够发出HTTP请求并针对我们API的响应进行断言。通常,我们不需要大量的库或工具即可完成这项工作。
在本文中,我们不会深入研究如何实现API集成测试,但是在进行框架迁移之前,您应该考虑编写这些测试。
从Express到Fastify的Fastify-Express过渡
将现有的Express应用程序迁移到完全不同的框架的想法似乎很艰巨。幸运的是,Fastify团队已经创建了一个插件-fastify-express-可帮助简化迁移路径。
该fastify-express
插件为Fastify添加了完全的Express兼容性。它提供了use()
一种可用于添加Express中间件和路由到Fastify服务器的方法。这使我们可以选择逐渐将现有Express应用程序的某些部分迁移到Fastify。
这是Express路由器的示例:
// src/routes.jsconst router = express.Router();router.get("/:user_id", function getUser(request, response, next) { response.json({});});export default router;
然后,我们可以使用fastify-express
将现有的Express路由器添加到Fastify服务器实例中:
// src/server.jsimport 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);
稍后再开始将应用程序迁移到Fastify时,我们将探索所有这些工作原理的细节。
重要的是要意识到使用fastify-express
插件不是一个长期的解决方案。如果我们想获得Fastify的全部好处,则需要在某些时候迁移Express特定的应用程序代码。但是,该fastify-express
插件为我们提供了分阶段迁移到Fastify的机会。
我们的示例快递应用
我们将构建一个Express应用程序示例,然后将其迁移以使用Fastify框架。现在让我们看一下它的代码。
所需的依赖项
首先,让我们创建一个新项目:
mkdir express-to-fastify-migrationcd express-to-fastify-migrationnpm init -y
然后,我们将在终端中运行此命令以安装Express应用程序所需的依赖项:
npm install express cors
最后,打开package.json
并在该scripts
部分上方添加以下行:
"type": "module",
这将使我们能够在应用程序中加载ES模块。
路由器模块
我们将创建一个Express路由器实例,以帮助我们封装路由和中间件。Express中的路由器可用于帮助我们将应用程序组织为离散的模块。例如,我们可能有一个路由器用于/user
路由,而另一台路由器用于/address
路由。稍后我们将看到这如何帮助我们分阶段将Express应用程序迁移到Fastify。
让我们创建一个路由器实例,并向其中添加一些中间件:
// src/routes.jsimport express from "express";import cors from "cors";const router = express.Router();router.use(express.json());router.use(cors({ origin: true }));
在上面的代码中,我们配置了两个Express中间件示例:
-
express.json()。此中间件功能内置于Express中。它处理解析JSON请求主体。
-
cors。该中间件帮助我们将CORS标头添加到我们的API响应中。它将允许从网页调用我们的API。
这些中间件工具将针对对我们在此路由器上定义的路由的任何请求运行。
现在我们已经配置了中间件,我们可以将第一条路由添加到路由器中:
// src/routes.jsrouter.post("/", function createUser(request, response, next) { const newUser = request.body; if (!newUser) { return next(new Error("Error creating user")); } response.status(201).json(newUser);});
在实际的应用程序中,上面的路由处理程序功能将验证其已接收的数据,然后调用数据库以创建新的用户记录。对于此示例,我们将发送作为响应正文接收到的数据。
现在,我们将添加一条检索用户的路由:
// src/routes.jsrouter.get("/:user_id", function getUser(request, response, next) { const user = { id: request.params.user_id, first_name: "Bobinsky", last_name: "Oso", }; response.json(user);});
与POST
路由一样,上面的路由处理程序通常会调用数据库以检索用户数据,但是对于本示例,我们已经硬编码了要发送到响应正文中的对象。
最后,我们将导出router
对象,以便可以将其导入另一个模块:
// src/routes.jsexport default router;
应用模块
现在,我们将创建一个应用模块:
// src/app.jsimport express from "express";import routes from "./routes.js";export default function buildApp() { const app = express(); app.use("/user", routes); return app;}
在这个模块中,我们定义一个函数来创建一个新的Express服务器实例。然后,我们将路由器对象添加到服务器实例。
服务器模块
最后,我们将创建一个服务器模块。该模块使用buildApp()
我们在应用程序模块中定义的功能来创建新的Express服务器实例。然后,通过将其配置为侦听端口来启动我们的Express服务器3000
:
// src/server.jsimport buildApp from "./app.js";const express = buildApp();express.listen(3000, () => { console.log("Example app listening at http://localhost:3000");});
运行我们的应用程序
现在,我们有了一个功能完整的Express应用程序,可以在终端中运行它:
node src/server.js
在单独的终端中,我们可以使用cURL向API发出请求,以确认其正常工作:
curl --verbose --request GET \ --url http://localhost:3000/user/3d395cb4-531c-4989-b8ed-9cc75198187e \ --header 'Origin: http://example-origin.com'
我们应该收到如下响应:
< HTTP/1.1 200 OK< X-Powered-By: Express< Access-Control-Allow-Origin: http://example-origin.com< Vary: Origin< Content-Type: application/json; charset=utf-8< {"id":"3d395cb4-531c-4989-b8ed-9cc75198187e","first_name":"Bobinsky","last_name":"Oso"}
将我们的应用程序从Express迁移到Fastify
现在我们有了功能齐全的Express应用程序,我们将迁移它以使用Fastify框架。
所需的依赖项
我们需要安装三个依赖项:
-
该Fastify框架
-
在fastify快车插件
-
在fastify-CORS插件-这是快递的端口
cors
,我们的应用程序已经在使用中间件
让我们在终端中运行以下命令来安装它们:
npm install fastify fastify-express fastify-cors
重构我们的应用程序模块
现在我们已经安装了依赖项,我们需要重构我们的应用程序模块。我们将其更改为:
-
导入
fastify
而fastify-express
不是express
-
创建一个Fastify服务器实例而不是一个Express服务器实例
-
使用
fastify-express
插件将我们的Express路由器对象添加到服务器
进行这些更改后,结果如下所示:
// src/app.jsimport Fastify from "fastify";import ExpressPlugin from "fastify-express";import routes from "./routes.js";export default async function buildApp() { const fastify = Fastify({ logger: true, }); await fastify.register(ExpressPlugin); fastify.use("/user", routes); return fastify;}
您会在上面的代码中注意到,当我们创建Fastify服务器实例时,我们正在传递logger选项。这将启用Fastify的内置日志记录功能。我们稍后将详细了解。
重构我们的服务器模块
现在,我们需要更改服务器模块以与Fastify服务器实例一起使用:
// src/server.jsimport buildApp from "./app.js";const fastify = await buildApp();try { await fastify.listen(3000);} catch (error) { fastify.log.error(error); process.exit(1);}
由于Fastify具有对Promise的本机支持,因此在上面的代码中,我们可以使用await
Fastify的内置日志记录功能来捕获并记录所有错误。
下一步
我们的应用程序现在正在使用Fastify路由请求和发送响应。它具有完整的功能,但我们的航线仍在使用Express。为了完全从Express迁移出去,我们还需要迁移路线以使用Fastify。
重构路线模块
Express应用程序中的路由封装在Express路由器中。我们将把该路由器重构为Fastify插件。插件是Fastify的一项功能,可让我们封装路由和任何相关功能。
我们将src/routes.js
通过删除一些Express特定行开始重构路由模块():
- import express from "express"- const router = express.Router();- router.use(express.json());
然后,我们需要将默认模块导出更改为async
接受Fastify服务器实例的函数。这是Fastify插件的基础。我们的routes模块中的其余代码将在此插件函数内移动:
export default async function routes(fastify) { // Configure routes}
为了使我们的中间件和路由能够与Fastify一起使用,我们需要进行以下更改:
-
router
提及fastify
-
路由处理程序的功能是
async
-
将处理程序函数参数从路由
(request, response, next)
到(request, reply)
-
response
提及reply
-
致电
response.json()
至reply.send()
-
的情况下
next(error)
,以throw error
完成所有这些更改之后,我们的路由模块现在是一个包含Fastify路由的Fastify插件:
// src/routes.jsimport cors from "cors";export default async function routes(fastify) { fastify.use(cors({ origin: true })); fastify.post("/", async function createUser(request, reply) { const newUser = request.body; if (!newUser) { throw new Error("Error creating user"); } reply.status(201).send(newUser); }); fastify.get("/:user_id", async function getUser(request, reply) { const user = { id: request.params.user_id, first_name: "Bobinsky", last_name: "Oso", }; reply.send(user); });}
现在,我们需要更改我们的应用程序模块(src/app.js
),以使用从路由模块中导出的插件。这意味着将fastify.use()
呼叫替换为fastify.register()
:
- fastify.use("/user", routes);+ fastify.register(routes, { prefix: "/user" });
我们的示例Express应用程序只有一个路由器,因此我们能够迁移应用程序中的所有路由以一次性使用Fastify。但是,如果我们有一个带有多个路由器的大型Express应用程序,则可以逐步将每个路由器一次迁移到Fastify上。
用插件替换中间件
我们的应用程序状态良好,我们几乎已将其从Express完全迁移到Fastify。还有一件事情要迁移:我们对cors
Express中间件软件包的使用。我们之前安装了fastify-cors
插件,现在需要在应用程序中添加它以替换cors
中间件。
在我们的路线模块(src/routes.js
),我们需要更换import
的的cors
中间件:
- import cors from "cors";+ import CorsPlugin from "fastify-cors";
然后,我们需要将的呼叫替换为fastify.use()
的呼叫fastify.register()
:
- fastify.use(cors({ origin: true }));+ fastify.register(CorsPlugin, { origin: true });
请注意,当我们在Fastify中注册插件时,我们需要将插件函数和options对象作为单独的参数传递。
由于我们不再使用插件提供的use()
功能,因此fastify-express
可以将其从应用程序中完全删除。为此,让我们从应用模块(src/app.js
)中删除以下几行:
- import ExpressPlugin from "fastify-express";- await fastify.register(ExpressPlugin);
删除Express依赖项
我们的应用程序从Express到Fastify的迁移已完成!现在,我们可以在终端中运行以下命令来删除与Express相关的依赖项:
npm uninstall express cors fastify-express
运行我们的迁移应用程序
现在我们已经将应用程序完全迁移到了Fastify,现在是时候检查一切是否仍在按我们期望的方式工作了。让我们运行与我们先前在应用程序中使用Express时运行的命令相同的命令。
首先,我们将在终端中运行该应用程序:
node src/server.js
然后,在另一个终端中,我们将使用cURL向API发出请求,以确认其是否按预期工作:
curl --verbose --request GET \ --url http://localhost:3000/user/3d395cb4-531c-4989-b8ed-9cc75198187e \ --header 'Origin: http://example-origin.com'
我们应该收到如下响应:
< HTTP/1.1 200 OK< vary: Origin< access-control-allow-origin: http://example-origin.com< content-type: application/json; charset=utf-8< {"id":"3d395cb4-531c-4989-b8ed-9cc75198187e","first_name":"Bobinsky","last_name":"Oso"}
Moving Away from Middleware
我们的示例Express应用程序仅使用了几个中间件功能,但现实世界中的Express应用程序可能会使用更多的中间件功能。如我们所见,该fastify-express
插件允许我们根据需要继续使用Express中间件。这使我们可以推迟将自己的自定义Express中间件重写为Fastify插件。但是,我们该如何替换第三方Express中间件呢?
对我们来说幸运的是,有一个健康的插件生态系统可用于Fastify。以下是一些流行的Express中间件软件包,我们可以用Fastify插件替换它们:
-
cors ➜ fastify-cors
-
helmet ➜ fastify-helmet
-
csurf ➜ fastify-csrf
-
express-session ➜ fastify-server-session
-
express-jwt ➜ fastify-jwt
-
http-errors ➜ fastify-sensible
-
serve-static ➜ fastify-static
-
multer ➜ fastify-multer
一些Fastify插件是Express对应产品的直接端口(或包装)。这意味着我们通常不需要更改传递给Fastify插件的配置选项。
充分利用固定
现在,我们已经开始通过迁移Express应用程序来熟悉Fastify,现在正是时候开始研究我们可以从中受益的其他Fastify功能。
验证
Fastify提供用于请求验证的功能。它在后台使用Ajv(另一个JSON模式验证器),这使我们可以使用JSON Schema定义验证规则。
这是一个使用JSON模式验证POST
路由上的请求正文的示例:
const schema = { body: { type: "object", required: ["first_name"], properties: { first_name: { type: "string", minLength: 1 }, }, },};app.post("/user", { schema }, async (request, reply) => { reply.send(request.body);});
验证错误会自动格式化并作为JSON响应发送:
{ "statusCode": 400, "error": "Bad Request", "message": "body should have required property 'first_name'"}
记录中
登录Node.js应用程序可能会对生产性能产生负面影响。这是因为将日志数据序列化和传输到其他地方(例如,到Elasticsearch)涉及许多步骤。高度优化我们应用程序的这一方面很重要。
日志记录完全集成在Fastify中,这意味着我们无需花时间选择和集成记录器。Fastify使用快速灵活的记录器:pino。它以JSON格式生成日志:
{"level":30,"time":1615881822269,"pid":14323,"hostname":"localhost","msg":"Server listening at http://127.0.0.1:3000"}{"level":30,"time":1615881829697,"pid":14323,"hostname":"localhost","reqId":"req-1","req":{"method":"GET","url":"/user/abc123","hostname":"localhost:3000","remoteAddress":"127.0.0.1","remotePort":38238},"msg":"incoming request"}{"level":30,"time":1615881829704,"pid":14323,"hostname":"localhost","reqId":"req-1","res":{"statusCode":200},"responseTime":6.576989000663161,"msg":"request completed"}
创建Fastify服务器实例时,我们可以启用日志记录并自定义传递给的选项pino
。然后,Fastify将自动输出如上所示的日志消息。记录器实例可在Fastify服务器实例(例如fastify.log.info("...")
)和所有Request对象(例如request.log.info("...")
)上使用。
在Fastify Logging文档中了解更多信息。
错误处理
Fastify提供了setErrorHandler()方法,该方法允许我们显式指定用于错误处理的函数。这与Express不同,在Express中,错误处理中间件只能通过其接受的参数(err, req, res, next
)加以区分,并且必须按特定顺序添加。
为了获得充分的灵活性,我们可以在不同的插件中指定不同的Fastify错误处理程序。
装饰工
装饰器是Fastify中的一项强大功能,它使我们能够自定义核心Fastify对象(例如我们的Fastify服务器实例)以及请求和答复对象。这是基本装饰器的示例:
fastify.register(async (fastify, options) => { fastify.decorate("yolo", () => { return { yo: "lo" }; }); fastify.get("/yolo", async function(request, reply) { // Our Fastify server instance is bound to `this` reply.send(this.yolo()); });});
装饰器使我们能够在整个Fastify应用程序中提供诸如数据库连接或视图引擎之类的功能。
结论
在本文中,我们学习了如何将现有的Node.js应用程序从Express迁移到Fastify。我们已经看到了该fastify-express
插件如何帮助我们逐步迁移现有应用程序。即使我们的部分应用程序仍在使用Express,这也使我们开始从Fastify提供的功能中受益。
当您从Express转到Fastify时,以下资源可能会有所帮助:
-
本文中的示例代码。探索代码并运行我们在本文中构建的应用程序。
-
整理文档。Fastify框架的全面文档。
-
加强生态系统。Fastify插件目录。查找替换Express Express中间件的插件非常方便。
-
固定示例应用程序。由Fastify的主要维护者之一创建的示例应用程序。它展示了Fastify的核心概念,最佳实践和建议。
-
固定社区Discord服务器。获得有关使用Fastify开发应用程序的帮助和建议的好地方。
网友评论文明上网理性发言 已有0人参与
发表评论: