跳至主要內容
版本:最新 (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 和回應 schema 都需要完整的 JSON Schema

從 v5 開始,Fastify 將要求 querystringparamsbody schema 使用完整的 JSON schema。請注意,jsonShortHand 選項也已移除。

如果使用預設的 JSON Schema 驗證器,您將需要為 querystringparamsbodyresponse schema 提供完整的 JSON schema,包括 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 Schema 驗證器以使用不同的格式,例如 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
});

parameters 物件不再具有原型

在 v4 中,parameters 物件具有原型。v5 中不再如此。這表示您不再能夠存取從 Object 繼承的 parameters 物件上的屬性,例如 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 };
});

這透過加強防範原型汙染攻擊來提高應用程式的安全性。

類型提供器現在區分驗證器和序列化器 schema

在 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() 方法的 variadic 引數簽名。這表示您不再能夠使用可變數量的引數來呼叫 .listen()

// v4
fastify.listen(8000)

將會變成

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

這在 v4 中已經被棄用為 FSTDEP011,因此您應該已經更新您的程式碼以使用新的簽名。

已移除直接傳回 trailers

在 v4 中,您可以直接從處理常式傳回 trailers。這在 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 之前註冊

exposeHeadRoutes: true 時,我們對自訂 HEAD 路由有更嚴格的要求。

當您提供自訂 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

v5 中已移除 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' };
});

這在 v4 中已經被棄用為 FSTDEP05,因此您應該已經更新您的程式碼以使用新的簽名。

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

v5 中已移除 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' };
});

這在 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以獲取更多詳細資訊。