跳至主要内容
版本: 最新版本 (v5.0.x)

V5 迁移指南

本指南旨在帮助您从 Fastify v4 迁移到 v5。

在迁移到 v5 之前,请确保您已修复 v4 中的所有弃用警告。所有 v4 中的弃用功能已移除,升级后将不再可用。

长期支持周期

Fastify v5 将仅支持 Node.js v20+。如果您正在使用旧版本的 Node.js,则需要升级到较新版本才能使用 Fastify v5。

Fastify v4 将继续支持到 2025 年 6 月 30 日。如果您无法升级,可以考虑从 HeroDevs 购买终止支持计划。

为什么是 Node.js v20?

Fastify v5 将仅支持 Node.js v20+,因为它与 v18 相比存在重大差异,例如对 node:test 的更好支持。这使我们能够提供更好的开发人员体验并简化维护。

Node.js v18 将于 2025 年 4 月 30 日退出长期支持,因此无论如何您都应该计划升级到 v20。

重大更改

querystringparamsbody 以及响应模式现在需要完整的 JSON 模式

从 v5 开始,Fastify 将需要为 querystringparamsbody 模式提供完整的 JSON 模式。请注意,jsonShortHand 选项也已移除。

如果使用默认的 JSON 模式验证器,则需要为 querystringparamsbodyresponse 模式提供完整的 JSON 模式,包括 type 属性。

// v4
fastify.get('/route', {
schema: {
querystring: {
name: { type: 'string' }
}
}
}, (req, reply) => {
reply.send({ hello: req.query.name });
});
// v5
fastify.get('/route', {
schema: {
querystring: {
type: 'object',
properties: {
name: { type: 'string' }
},
required: ['name']
}
}
}, (req, reply) => {
reply.send({ hello: req.query.name });
});

有关更多详细信息,请参阅 #5586

请注意,仍然可以覆盖 JSON 模式验证器以使用不同的格式,例如 Zod。此更改也简化了这一点。

此更改有助于与其他工具集成,例如 @fastify/swagger

新的日志记录器构造函数签名

在 Fastify v4 中,Fastify 在 logger 选项中接受用于构建 pino 日志记录器的选项,以及自定义日志记录器实例。这是造成大量混淆的根源。

因此,logger 选项在 v5 中将不再接受自定义日志记录器。要使用自定义日志记录器,应改用 loggerInstance 选项

// v4
const logger = require('pino')();
const fastify = require('fastify')({
logger
});
// v5
const loggerInstance = require('pino')();
const fastify = require('fastify')({
loggerInstance
});

useSemicolonDelimiter 默认值为 false

从 v5 开始,Fastify 实例将不再默认支持在查询字符串中使用分号分隔符,就像在 v4 中一样。这是因为它是非标准行为,不符合 RFC 3986

如果您仍然希望使用分号作为分隔符,可以通过在服务器配置中设置 useSemicolonDelimiter: true 来实现。

const fastify = require('fastify')({
useSemicolonDelimiter: true
});

参数对象不再具有原型

在 v4 中,parameters 对象具有原型。在 v5 中不再如此。这意味着您将无法再访问从 parameters 对象上的 Object 继承的属性,例如 toStringhasOwnProperty

// 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 };
});

通过加强对原型污染攻击的防御,提高了应用程序的安全性。

类型提供程序现在区分验证器和序列化器模式

在 v4 中,类型提供程序对验证和序列化都使用相同的类型。在 v5 中,类型提供程序已拆分为两种独立的类型:ValidatorSchemaSerializerSchema

@fastify/type-provider-json-schema-to-ts@fastify/type-provider-typebox 已经更新:升级到最新版本以获取新的类型。如果您正在使用自定义类型提供程序,则需要像下面这样修改它

--- a/index.ts
+++ b/index.ts
@@ -11,7 +11,8 @@ import {
import { FromSchema, FromSchemaDefaultOptions, FromSchemaOptions, JSONSchema } from 'json-schema-to-ts'

export interface JsonSchemaToTsProvider<
Options extends FromSchemaOptions = FromSchemaDefaultOptions
> extends FastifyTypeProvider {
- output: this['input'] extends JSONSchema ? FromSchema<this['input'], Options> : unknown;
+ validator: this['schema'] extends JSONSchema ? FromSchema<this['schema'], Options> : unknown;
+ serializer: this['schema'] extends JSONSchema ? FromSchema<this['schema'], Options> : unknown;
}

.listen() 方法的更改

已移除 .listen() 方法的可变参数签名。这意味着您将无法再使用可变数量的参数调用 .listen()

// v4
fastify.listen(8000)

将变为

// v5
fastify.listen({ port: 8000 })

这在 v4 中已被弃用为 FSTDEP011,因此您应该已经更新了代码以使用新的签名。

已移除直接返回拖车

在 v4 中,您可以直接从处理程序返回拖车。在 v5 中,这不再可能。

// 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,因此您应该已经更新了代码以使用新的签名。

简化对路由定义的访问

所有与访问路由定义相关的弃用属性都已移除,现在可以通过 request.routeOptions 访问。

代码描述如何解决讨论
FSTDEP012您正在尝试访问已弃用的 request.context 属性。使用 request.routeOptions.configrequest.routeOptions.schema#4216 #5084
FSTDEP015您正在访问已弃用的 request.routeSchema 属性。使用 request.routeOptions.schema#4470
FSTDEP016您正在访问已弃用的 request.routeConfig 属性。使用 request.routeOptions.config#4470
FSTDEP017您正在访问已弃用的 request.routerPath 属性。使用 request.routeOptions.url#4470
FSTDEP018您正在访问已弃用的 request.routerMethod 属性。使用 request.routeOptions.method#4470
FSTDEP019您正在访问已弃用的 reply.context 属性。使用 reply.routeOptions.configreply.routeOptions.schema#5032 #5084

有关更多信息,请参阅 #5616

reply.redirect() 具有新的签名

reply.redirect() 方法具有新的签名:reply.redirect(url: string, code?: number)

// v4
reply.redirect(301, '/new-route')

更改为

// v5
reply.redirect('/new-route', 301)

这在 v4 中已被弃用为 FSTDEP021,因此您应该已经更新了代码以使用新的签名。

现在禁止修改 reply.sent

在 v4 中,您可以修改 reply.sent 属性以阻止发送响应。在 v5 中,这不再可能,请改用 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,因此您应该已经更新了代码以使用新的签名。

路由版本控制签名更改的约束

我们更改了路由版本控制约束的签名。versionversioning 选项已被移除,您应该改用 constraints 选项。

代码描述如何解决讨论
FSTDEP008您正在通过路由 {version: "..."} 选项使用路由约束。使用 {constraints: {version: "..."}} 选项。#2682
FSTDEP009您正在通过服务器 {versioning: "..."} 选项使用自定义路由版本控制策略。使用 {constraints: {version: "..."}} 选项。#2682

exposeHeadRoutes: true 时,HEAD 路由需要在 GET 之前注册

对于自定义 HEAD 路由,当 exposeHeadRoutes: true 时,我们有更严格的要求。

当您提供自定义 HEAD 路由时,必须显式将 exposeHeadRoutes 设置为 false

// v4
fastify.get('/route', {

}, (req, reply) => {
reply.send({ hello: 'world' });
});

fastify.head('/route', (req, reply) => {
// ...
});
// 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', {

}, (req, reply) => {
reply.send({ hello: 'world' });
});

此更改在 #2700 中进行,旧行为在 v4 中已被弃用为 FSTDEP007

已移除 request.connection

request.connection 属性已在 v5 中移除。您应该改用 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' };
});

这在 v4 中已被弃用为 FSTDEP05,因此您应该已经更新了代码以使用新的签名。

reply.getResponseTime() 已移除,请改用 reply.elapsedTime

reply.getResponseTime() 方法已在 v5 中移除。您应该使用 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' };
});

这在 v4 中已被弃用为 FSTDEP20,因此您应该已经更新了代码以使用新的签名。

fastify.hasRoute() 现在匹配 find-my-way 的行为

fastify.hasRoute() 方法现在匹配 find-my-way 的行为,并要求传递路由定义,如同在路由中定义的那样。

// 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

移除一些非标准的 HTTP 方法

我们已从 Fastify 中移除以下 HTTP 方法:

  • PROPFIND
  • PROPPATCH
  • MKCOL
  • COPY
  • MOVE
  • LOCK
  • UNLOCK
  • TRACE
  • SEARCH

现在可以使用 acceptHTTPMethod 方法将它们添加回来。

const fastify = Fastify()

// add a new http method on top of the default ones:
fastify.acceptHTTPMethod('REBIND')

// add a new HTTP method that accepts a body:
fastify.acceptHTTPMethod('REBIND', { hasBody: true })

// reads the HTTP methods list:
fastify.supportedMethods // returns a string array

更多信息请参见 #5567

移除装饰器中对引用类型的支持

使用引用类型(ArrayObject)装饰 Request/Reply 现在是被禁止的,因为此引用在所有请求之间共享。

// v4
fastify.decorateRequest('myObject', { hello: 'world' });
// 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' }
}
});

更多信息请参见 #5462

移除对带有 Content-Type: application/json 头部和空体的 DELETE 请求的支持

在 v4 中,Fastify 允许带有 Content-Type: application/json 头部和空体的 DELETE 请求。这在 v5 中不再允许。

更多信息请参见 #5419

插件不能再混合使用回调/Promise API

在 v4 中,插件可以混合使用回调和 Promise API,这会导致意外的行为。这在 v5 中不再允许。

// v4
fastify.register(async function (instance, opts, done) {
done();
});
// v5
fastify.register(async function (instance, opts) {
return;
});

// v5
fastify.register(function (instance, opts, done) {
done();
});

移除 getDefaultRoutesetDefaultRoute 方法

getDefaultRoutesetDefaultRoute 方法已在 v5 中移除。

更多信息请参见 #4485#4480。这在 v4 中已被弃用为 FSTDEP014,因此您应该已经更新了代码。

新特性

诊断通道支持

Fastify v5 现在原生支持 诊断通道 API,并提供了一种跟踪请求生命周期的方法。

'use strict'

const diagnostics = require('node:diagnostics_channel')
const sget = require('simple-get').concat
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 is the same as the one emitted by the 'tracing:fastify.request.handler:start' channel
console.log(msg)
})

diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => {
// in case of error
})

const fastify = Fastify()
fastify.route({
method: 'GET',
url: '/:id',
handler: function (req, reply) {
return { hello: 'world' }
}
})

fastify.listen({ port: 0 }, function () {
sget({
method: 'GET',
url: fastify.listeningOrigin + '/7'
}, (err, response, body) => {
t.error(err)
t.equal(response.statusCode, 200)
t.same(JSON.parse(body), { hello: 'world' })
})
})

更多详细信息请参见 文档#5252