
Fastify v5 迁移完全指南v4 到 v5 的全部破坏性变更、废弃项清理与新特性详解【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastifyFastify v5 是一个面向 Node.js v20 的主要版本升级它删除了 v4 中全部已废弃 API并对 Schema 校验、路由约束、日志、诊断与安全等多个层面做出了破坏性调整。本文以 docs/Guides/Migration-Guide-V5.md 为骨架结合当前仓库中 fastify.js、lib/logger-factory.js、lib/handle-request.js、lib/route.js 等源码与对应测试逐条给出 v4 与 v5 的代码对照、修复方式与底层原理帮助你在升级前一次性清理代码、规避运行期报错并第一时间用上 v5 新增的原生 Diagnostics Channel 追踪能力。说明本文针对的迁移目标为 Fastify v5文档正文中 v4→v5 的对照写法均已给出可运行样例。当前仓库快照的 package.json 主版本号已处于 v5 之后的开发线上因此 v5 中的各条新行为已在仓库代码与测试中得到落实可作为核对依据。升级前的两个前提先清空 v4 的全部废弃警告本指南开篇即强调在迁移到 v5 之前必须修复 v4 暴露出的所有 deprecation 警告。v5 已将 v4 中标记废弃的 API 全部移除升级后它们将不再工作直接抛错或行为失效而不是仅仅打印警告。废弃警告多以FSTDEPXXX编号形式出现在日志中。v5 的破坏性变更清单里几乎每一项都对应 v4 中已发出的FSTDEP编号详见文末“废弃编号速查表”。如果你在 v4 运行时看不到任何FSTDEP警告说明代码大体已就绪反之请先按警告提示逐个改造再执行 v5 升级。支持周期只支持 Node.js v20Fastify v5仅支持 Node.js v20。原因在于 Node.js v20 与 v18 存在显著差异例如对node:test的支持更好v5 团队希望借此提供更优的开发体验并精简维护工作。同时 Node.js v18 已于 2025 年 4 月 30 日退出长期支持LTS官方也建议顺势升级到 v20。Fastify v4 则仍被支持到2025 年 6 月 30 日若届时仍无法完成升级需自行评估额外的 EOLEnd-of-Life支持方案。Breaking Changes破坏性变更逐一对照1.querystring、params、body与response必须使用完整 JSON Schema从 v5 开始使用默认 JSON Schema 校验器时querystring、params、body以及responseschema都必须提供包含type属性的完整 JSON Schema。同时简写形式的jsonShortHand选项也被移除。v4 写法属性直接放在 Schema 顶层依赖隐式推导// v4 fastify.get(/route, { schema: { querystring: { name: { type: string } } } }, (req, reply) { reply.send({ hello: req.query.name }); });v5 写法必须显式声明对象结构与必填项// v5 fastify.get(/route, { schema: { querystring: { type: object, properties: { name: { type: string } }, required: [name] } } }, (req, reply) { reply.send({ hello: req.query.name }); });变更理由与该变更的实际收益主要体现在三方面简化替换式校验器仍可用自定义校验器例如接入 Zod完整的 JSON Schema 结构使这一替换更直接提升工具链互操作性完整 Schema 便于fastify/swagger等工具直接生成 OpenAPI 文档无需再猜测字段归属消除隐式推导顶层裸属性写法此前依赖 Fastify 内部对“这是某个属性的校验子”的推断去掉它后行为可预期。注意点这一条同时作用于输入校验querystring/params/body与响应序列化校验response升级时请逐一检查所有路由的 schema 定义。相关变更背景可参见仓库中 lib/config-validator.js 与 lib/validation.js 对 schema 的解析与校验路径。2.logger选项不再接受自定义 logger 实例请改用loggerInstancev4 中logger选项既可以放 pino 的构建参数也可以直接塞一个自定义 logger 实例语义混叠是混乱的主要来源。v5 明确拆分了二者logger只接收 pino 的 options或布尔值自定义实例一律通过loggerInstance传入。// v4 —— 不再支持 const logger require(pino)(); const fastify require(fastify)({ logger });// v5 const loggerInstance require(pino)(); const fastify require(fastify)({ loggerInstance });底层实现印证在 lib/logger-factory.js 中构建逻辑会对options.logger与options.loggerInstance分别校验——两者同时提供会报错而loggerInstance传入的自定义实例会接管输出管道与序列化器最终写入内部 logger 供 fastify.js 使用。仓库内 test/logger 与 test/conditional-pino.test.js 等测试文件对该拆分后的各种组合无 logger、纯 options、纯实例、二者兼有均有覆盖。3.useSemicolonDelimiter默认关闭v4 默认支持用分号;作为 query string 的分隔符这属于非标准行为不符合 RFC 3986 第 3.4 节对 query 组件的定义。v5 起该行为默认关闭如需保留分号分隔请在服务端配置中显式开启const fastify require(fastify)({ useSemicolonDelimiter: true });升级后若发现某些依赖“分号拼接 query”的旧请求解析异常例如?a1;b2被整体当作一个参数要么开启该选项要么改造客户端使用标准分隔。仓库中 test/use-semicolon-delimiter.test.js 覆盖了默认关闭与显式开启两种路径。4.params路径参数对象不再携带原型v4 中路由的参数对象是带原型的普通对象因此可以直接调用继承自Object的方法如toString、hasOwnProperty。v5 中参数对象改为无原型对象null-prototype这类方法不再可用。// v4 fastify.get(/route/:name, (req, reply) { console.log(req.params.hasOwnProperty(name)); // true return { hello: req.params.name }; });// v5 fastify.get(/route/:name, (req, reply) { console.log(Object.hasOwn(req.params, name)); // true return { hello: req.params.name }; });这是一项安全加固去掉原型可以削弱原型污染prototype pollution攻击面——攻击者即使让__proto__等键进入参数对象也不会沿原型链扩散污染全局对象。社区中同样策略也应用于 JSON 解析参见仓库 docs/Guides/Prototype-Poisoning.md 相关防护讨论。若代码中曾直接使用req.params.hasOwnProperty(...)、req.params.toString()等调用请改用Object.hasOwn(req.params, key)或先做Object.create(null)之外的标准判断。5. Type Provider 拆分ValidatorSchema与SerializerSchemav4 中类型提供器Type Provider对校验与序列化使用同一套类型映射。v5 拆分为两种独立类型ValidatorSchema与SerializerSchema底层约束接口从input/output调整为schema/validator/serializer。官方维护的两个类型提供器已同步更新升级到最新版本即可获得新类型fastify/type-provider-json-schema-to-tsfastify/type-provider-typebox若使用自定义 Type Provider需按其新接口改造diff 示意如下export interface JsonSchemaToTsProvider Options extends FromSchemaOptions FromSchemaDefaultOptions extends FastifyTypeProvider { - output: this[input] extends JSONSchema ? FromSchemathis[input], Options : unknown; validator: this[schema] extends JSONSchema ? FromSchemathis[schema], Options : unknown; serializer: this[schema] extends JSONSchema ? FromSchemathis[schema], Options : unknown; }这意味着同一份 schema 今后可以分别推导出“校验器类型”与“序列化器类型”避免在响应序列化如日期格式、额外字段裁剪与入参校验使用不同处理时类型失真。类型层面的详细接入方式可参考仓库 docs/Reference/Type-Providers.md、types/type-provider.d.ts 与 test/types/type-provider.tst.ts。6..listen()不再支持可变参数签名v5 移除了.listen()的可变参数形式只能以对象传参// v4 —— 不再支持 fastify.listen(8000)// v5 fastify.listen({ port: 8000 })该变体早在 v4 中就以FSTDEP011标记废弃因此绝大多数代码应已迁移。回调式的listen(opts, cb)与 Promise 式用法不受影响。完整选项host、backlog、exclusive等可查阅 docs/Reference/Server.md。7. 不再允许直接从 handler 同步返回 trailersv4 中注册 trailer 的函数可以同步返回字符串。v5 要求 trailer 函数使用async 函数或回调形式// v4 fastify.get(/route, (req, reply) { reply.trailer(ETag, function (reply, payload) { return custom-etag }) reply.send() });// v5 fastify.get(/route, (req, reply) { reply.trailer(ETag, async function (reply, payload) { return custom-etag }) reply.send() });同步直接返回在 v4 已按FSTDEP013标记废弃。HTTP trailer 的计算本身是异步写入连接末尾的过程统一异步签名可避免返回值在发送时序上的歧义。8. 路由定义访问收口到request.routeOptionsv5 把所有“访问路由定义”的历史入口统一收口为request.routeOptions旧入口全部移除。下表总结了各废弃编号的迁移路径废弃编号说明迁移方式FSTDEP012访问废弃的request.context改用request.routeOptions.config或request.routeOptions.schemaFSTDEP015访问废弃的request.routeSchema改用request.routeOptions.schemaFSTDEP016访问废弃的request.routeConfig改用request.routeOptions.configFSTDEP017访问废弃的request.routerPath改用request.routeOptions.urlFSTDEP018访问废弃的request.routerMethod改用request.routeOptions.methodFSTDEP019访问废弃的reply.context改用reply.routeOptions.config或reply.routeOptions.schema在编码层面对request.context的移除是刻意为之——旧实现把路由上下文绑定在请求对象内部属性上封装边界模糊routeOptions则是一份面向用户的、稳定的描述结构。9.reply.redirect()新签名redirect(url, code?)参数顺序发生了对调状态码变为可选// v4 reply.redirect(301, /new-route)// v5 reply.redirect(/new-route, 301)该改动在 v4 中已以FSTDEP021标记废弃。改造时注意默认状态码为 302显式传code时先 url 后 code。10. 禁止直接改reply.sent请使用reply.hijack()v4 中开发者可用reply.sent true手动接管响应阻止 Fastify 自动发送。v5 禁止该赋值接管底层 socket 的正确方式是调用reply.hijack()// v4 fastify.get(/route, (req, reply) { reply.sent true; reply.raw.end(hello); });// v5 fastify.get(/route, (req, reply) { reply.hijack(); reply.raw.end(hello); });该写法在 v4 中已以FSTDEP010标记废弃。hijack()语义更明确声明“此响应由我完全接管”Fastify 此后不再触碰该连接的写入。仓库中 test/skip-reply-send.test.js 等测试体现了接管场景。11. 路由版本约束统一改用constraintsv5 移除了路由级version选项与服务级versioning策略选项改用通用的constraints机制废弃编号说明迁移方式FSTDEP008在路由上使用{version: ...}选项改用{constraints: {version: ...}}FSTDEP009通过服务端{versioning: ...}配置自定义版本策略改用{constraints: {version: ...}}示例迁移// v4 fastify.get(/route, { version: 1.0.0 }, handler) // v5 fastify.get(/route, { constraints: { version: 1.0.0 } }, handler)constraints是底层路由库 find-my-way 的通用约束概念统一后也方便自定义其他约束维度。12.exposeHeadRoutes: true时自定义HEAD路由必须先于GET注册或显式关闭当开启exposeHeadRoutes: true默认开启时v5 对“同时自定义 HEAD”的场景要求更严格只有两种合法姿势方式一在 GET 上显式关闭自动 HEAD 暴露再自定义 HEAD// v5 fastify.get(/route, { exposeHeadRoutes: false }, (req, reply) { reply.send({ hello: world }); }); fastify.head(/route, (req, reply) { // ... });方式二把HEAD路由放在GET之前注册// v5 fastify.head(/route, (req, reply) { // ... }); fastify.get(/route, { // 这里不要再写 exposeHeadRoutes: false }, (req, reply) { reply.send({ hello: world }); });v4 中的旧顺序行为已按FSTDEP007废弃。原因在于自动生成的HEAD路由与用户自定义HEAD存在“同 url 同方法”冲突v5 用确定的注册顺序消除歧义。13. 移除request.connection统一使用request.socket// v4 fastify.get(/route, (req, reply) { console.log(req.connection.remoteAddress); return { hello: world }; });// v5 fastify.get(/route, (req, reply) { console.log(req.socket.remoteAddress); return { hello: world }; });request.connection是 Node.js 中早已废弃的别名v4 中以FSTDEP05标记废弃v5 彻底移除。14. 移除reply.getResponseTime()改用reply.elapsedTime// v4 fastify.get(/route, (req, reply) { console.log(reply.getResponseTime()); return { hello: world }; });// v5 fastify.get(/route, (req, reply) { console.log(reply.elapsedTime); return { hello: world }; });getResponseTime()方法改为属性elapsedTime毫秒语义一致但更符合属性访问习惯v4 中已以FSTDEP20原文编号写作FSTDEP20为FSTDEP020的别名指代废弃。15.hasRoute()与 find-my-way 行为对齐fastify.hasRoute()现在要求传入的url与路由定义时完全一致即原始路由模式串而非匹配后的真实请求路径// v4 —— 传真实路径也可命中 fastify.get(/example/:file(^\\d).png, function (request, reply) { }) console.log(fastify.hasRoute({ method: GET, url: /example/12345.png })); // true// v5 —— 需传路由定义串 fastify.get(/example/:file(^\\d).png, function (request, reply) { }) console.log(fastify.hasRoute({ method: GET, url: /example/:file(^\\d).png })); // true这意味着hasRoute变成一次纯粹的“路由表查找”不再需要经过匹配引擎执行语义上更贴近底层 find-my-way 的hasRoute行为也更可预期。仓库中 test/has-route.test.js 覆盖了含正则参数路由的查询场景。16. 移除部分非标准 HTTP 方法用addHttpMethod加回v5 从默认支持列表中移除了以下 HTTP 方法PROPFINDPROPPATCHMKCOLCOPYMOVELOCKUNLOCKTRACESEARCH这些方法大多来自 WebDAV 等领域扩展不是通用 HTTP 方法。若确有需要可通过addHttpMethod重新注册并可指定hasBody、overrideExisting等选项const fastify Fastify() // 在默认方法之上新增一个 HTTP 方法 fastify.addHttpMethod(REBIND) // 新增一个可携带请求体的 HTTP 方法 fastify.addHttpMethod(REBIND, { hasBody: true }) // 读取当前支持的 HTTP 方法列表 fastify.supportedMethods // 返回字符串数组源码佐证在 fastify.js 中addHttpMethod内部通过维护bodyless与bodywith两套方法集合来决定一个方法是否默认消费请求体并自动为小写方法名如rebind注册速记路由装饰器。仓库内 test/http-methods/custom-http-methods.test.js 以及 test/http-methods 下各方法独立测试文件如 test/http-methods/trace.test.js、test/http-methods/copy.test.js对加回与禁用的行为均有验证。TypeScript 类型同步维护在 types/instance.d.ts。17. 禁止用引用类型装饰 Request/Replyv5 不再允许用引用类型Array、Object等可变对象直接装饰 Request 或 Reply原因是该引用会被所有请求共享极易造成跨请求数据泄漏。需要为每个请求准备独立对象时有三种替代写法写法一先占位在请求钩子中按需初始化// v5 fastify.decorateRequest(myObject); fastify.addHook(onRequest, async (req, reply) { req.myObject { hello: world }; });写法二装饰为工厂函数每次访问时返回新对象// v5 fastify.decorateRequest(myObject, () ({ hello: world }));写法三使用 getter// v5 fastify.decorateRequest(myObject, { getter () { return { hello: world } } });同理适用于decorateReply。这一条是典型的安全与正确性修复把“实例级共享引用”误当成“请求级数据”是最常见的 Fastify 误用v5 从 API 层面直接堵死。仓库中 test/decorator.test.js、test/decorator-instance-properties.test.js 及相关实现 lib/decorate.js 可查证装饰行为。18. DELETE Content-Type: application/json 空 body 不再被接受v4 中DELETE请求携带Content-Type: application/json且请求体为空时会被接受v5 起将不再允许这种自相矛盾的请求。若客户端存在“只发空 JSON 头不发 body”的 DELETE 调用请改造成真正发送 JSON body或去掉该 Content-Type。19. 插件禁止混用 callback 与 Promise APIv4 中fastify.register()可以接收一个 async 函数同时又调用done()两种完成机制叠加会产生不可预期的时序行为。v5 强制二选一// v4 —— 不再允许async 函数体内又调用 done() fastify.register(async function (instance, opts, done) { done(); });// v5 —— 用 async 函数就返回或 await 完成后自然结束 fastify.register(async function (instance, opts) { return; });或// v5 —— 用回调风格就只调 done() fastify.register(function (instance, opts, done) { done(); });注意回调风格插件若在done()之后又抛出异常也将被视为非法。Fastify 基于 avvio 的加载器据此可以获得确定性的启动完成信号避免“回调已结束但 Promise 仍在后台推进”的竞态。20. 请求对象新增host、hostname、port且hostname不再携带端口这是请求头语义向 Node.jsURL对象对齐的改动v4 中req.hostname包含主机名和端口例如本地可能是localhost:1234v5 中req.host与 v4 的req.hostname取值相同含端口如localhost:1234req.hostname仅主机名不包含端口req.port仅端口号。升级后凡用于拼接 URL、做 Host 白名单校验或生成重定向地址的代码务必重新审视取值来源——尤其是把hostname直接塞进Location头的场景避免出现端口丢失或重复端口。21. 移除getDefaultRoute/setDefaultRoute服务端自定义“未匹配路由兜底处理”的getDefaultRoute/setDefaultRoute方法已在 v5 移除v4 中已以FSTDEP014标记废弃。404 行为应改用统一的错误处理与自定义 404 机制参见 docs/Reference/Server.md 与 lib/four-oh-four.js。22.time与date-time格式强制要求时区v5 升级了 AJV 编译链路随之更新的ajv-formats对time与date-time两种格式强制要求包含时区否则校验失败。若需要兼容“无时区”的历史数据官方建议改用iso-time与iso-date-time格式支持可选的时区后缀。// v5 中若旧数据没有时区 // format: date-time → 校验失败 // format: iso-date-time → 兼容无时区写法这一改动主要影响严格入参校验的场景比如接收前端日期字符串的 API。审计所有引用format: time/format: date-time的 schema按需切换为iso-*变体或要求上游补齐时区。新特性原生 Diagnostics Channel 支持v5 最大亮点之一是原生支持 Node.js Diagnostics Channel可对外发布请求生命周期事件用于链路追踪与可观测性接入且无需任何额外依赖。以下示例订阅了路由 handler 的开始与结束事件并打印路由信息use strict const diagnostics require(node:diagnostics_channel) const Fastify require(fastify) diagnostics.subscribe(tracing:fastify.request.handler:start, (msg) { console.log(msg.route.url) // /:id console.log(msg.route.method) // GET }) diagnostics.subscribe(tracing:fastify.request.handler:end, (msg) { // msg 与 tracing:fastify.request.handler:start 通道发出的是同一个对象 console.log(msg) }) diagnostics.subscribe(tracing:fastify.request.handler:error, (msg) { // 出错时触发 }) const fastify Fastify() fastify.route({ method: GET, url: /:id, handler: function (req, reply) { return { hello: world } } }) fastify.listen({ port: 0 }, async function () { const result await fetch(fastify.listeningOrigin /7) t.assert.ok(result.ok) t.assert.strictEqual(response.status, 200) t.assert.deepStrictEqual(await result.json(), { hello: world }) })源码层面的印证Fastify 在 lib/handle-request.js 顶部直接require(node:diagnostics_channel)并基于tracingChannel(fastify.request.handler)建立了结构化追踪通道start/end/error语义由此而来。也就是说你可以不依赖任何第三方 APM 库仅用 Node 内置 API 就实现针对业务 handler 的执行追踪。仓库中 test/diagnostics-channel 目录对这条链路做了系统性验证例如test/diagnostics-channel/404.test.js未匹配路由时的通道行为test/diagnostics-channel/async-error-handler.test.js异步错误路径test/diagnostics-channel/error-before-handler.test.jshandler 执行前出错test/diagnostics-channel/error-status.test.js 与 test/diagnostics-channel/error-request.test.js。使用 Hooks 场景下的通道接入细节可继续阅读 docs/Reference/Hooks.md。废弃编号速查表将上文散落的废弃编号汇总便于升级时对照日志定位编号移除/变更内容迁移目标FSTDEP007自定义 HEAD 与 GET 的注册顺序先注册 HEAD或在 GET 上exposeHeadRoutes: falseFSTDEP008路由级version选项constraints: { version }FSTDEP009服务级versioning选项constraints: { version }FSTDEP010直接赋值reply.sentreply.hijack()FSTDEP011.listen(8000)可变参数签名.listen({ port: 8000 })FSTDEP012request.contextrequest.routeOptions.config/.schemaFSTDEP013trailer 函数同步直接返回async 函数或回调FSTDEP014getDefaultRoute/setDefaultRoute统一错误处理与自定义 404FSTDEP015request.routeSchemarequest.routeOptions.schemaFSTDEP016request.routeConfigrequest.routeOptions.configFSTDEP017request.routerPathrequest.routeOptions.urlFSTDEP018request.routerMethodrequest.routeOptions.methodFSTDEP019reply.contextreply.routeOptions.config/.schemaFSTDEP020/021getResponseTime()/redirect(code, url)elapsedTime/redirect(url, code)FSTDEP05request.connectionrequest.socket备注指南原文编号写法不完全统一如FSTDEP05、FSTDEP20上表按文档语义归一化处理实际以运行期日志输出为准。推荐的升级执行顺序综合上述变更一次稳妥的 v4 → v5 升级建议按如下顺序推进环境准备将 Node.js 升级到 v20可用node -v确认并核对 CI 与部署镜像中的 Node 版本清理 v4 废弃先在 v4 下运行完整测试与压测捕获所有FSTDEPXXX警告逐个按上表迁移确保升级前日志零废弃警告补齐 Schema全文检索schema: { querystring / params / body / response }为每个区块补充type、properties、required同时检查format: date-time/time是否触发新格式校验构造选项迁移自定义 logger 迁移到loggerInstance将日志配置与初始化代码对照 lib/logger-factory.js 校验逻辑复查路由与请求/响应 API 迁移版本约束改constraints、HEAD 顺序调整、request.socket、reply.hijack()、redirect(url, code)、reply.elapsedTime、routeOptions.*替换插件与装饰器改造注册函数二选一async 或 done、Request/Reply 装饰改用函数/getter/钩子内初始化依赖升级将 Type Provider、Swagger、日志等生态依赖升级到支持 v5 的版本行为回归用仓库级测试思路回归关键路径例如有路由自定义 HTTP 方法时参考 test/http-methods/custom-http-methods.test.js涉及分号分隔符参考 test/use-semicolon-delimiter.test.js涉及路由存在性判断参考 test/has-route.test.js。Fastify v5 用“一次彻底的破坏性收敛”换取了更清晰、更安全的 API 边界完整 JSON Schema、loggerInstance、routeOptions、constraints、无原型 params 与原生 Diagnostics Channel都指向更确定的行为与更强的可观测性。完成上述迁移后你的代码将处于一条更长期、更健康的技术基线之上。【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考