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。
重大更改
querystring
、params
和 body
以及响应模式现在需要完整的 JSON 模式
从 v5 开始,Fastify 将需要为 querystring
、params
和 body
模式提供完整的 JSON 模式。请注意,jsonShortHand
选项也已移除。
如果使用默认的 JSON 模式验证器,则需要为 querystring
、params
、body
和 response
模式提供完整的 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
继承的属性,例如 toString
或 hasOwnProperty
。
// 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 中,类型提供程序已拆分为两种独立的类型:ValidatorSchema
和 SerializerSchema
。
@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.config 或 request.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.config 或 reply.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
,因此您应该已经更新了代码以使用新的签名。
路由版本控制签名更改的约束
我们更改了路由版本控制约束的签名。version
和 versioning
选项已被移除,您应该改用 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。
移除装饰器中对引用类型的支持
使用引用类型(Array
、Object
)装饰 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();
});
移除 getDefaultRoute
和 setDefaultRoute
方法
getDefaultRoute
和 setDefaultRoute
方法已在 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' })
})
})