ルート
ルート
ルートメソッドはアプリケーションのエンドポイントを設定します。Fastifyでは、簡略メソッドと完全宣言の2つの方法でルートを宣言できます。
完全宣言
fastify.route(options)
ルートオプション
method
: 現在、GET
、HEAD
、TRACE
、DELETE
、OPTIONS
、PATCH
、PUT
、POST
をサポートしています。さらに多くのメソッドを受け入れるには、addHttpMethod
を使用する必要があります。メソッドの配列にすることもできます。url
: このルートに一致するURLのパス(別名:path
)。schema
: リクエストとレスポンスのスキーマを含むオブジェクト。 JSON スキーマ 形式にする必要があります。詳細は こちら を参照してください。body
: POST、PUT、PATCH、TRACE、SEARCH、PROPFIND、PROPPATCH、またはLOCKメソッドの場合、リクエストの本文を検証します。querystring
またはquery
: クエリ文字列を検証します。これは、type
プロパティがobject
で、properties
オブジェクトがパラメータの完全なJSONスキーマオブジェクト、または下記のようにproperties
オブジェクトに含まれる値を単純に指定できます。params
: パラメータを検証します。response
: レスポンスのスキーマをフィルタリングして生成します。スキーマを設定することで、スループットを10~20%向上させることができます。
exposeHeadRoute
: すべてのGET
ルートに対して、兄弟のHEAD
ルートを作成します。exposeHeadRoutes
インスタンスオプションの値をデフォルトで使用します。このオプションを無効にせずにカスタムHEAD
ハンドラーが必要な場合は、GET
ルートの前に定義してください。attachValidation
: スキーマ検証エラーがある場合、エラーをエラーハンドラーに送信する代わりに、validationError
をリクエストにアタッチします。デフォルトのエラー形式はAjvの形式です。onRequest(request, reply, done)
: リクエストを受信するとすぐに呼び出される関数。関数の配列にすることもできます。preParsing(request, reply, done)
: リクエストの解析前に呼び出される関数。関数の配列にすることもできます。preValidation(request, reply, done)
: 共通のpreValidation
フックの後で呼び出される関数。例えば、ルートレベルで認証を実行する必要がある場合に役立ちます。関数の配列にすることもできます。preHandler(request, reply, done)
: リクエストハンドラーの直前に呼び出される関数。関数の配列にすることもできます。preSerialization(request, reply, payload, done)
: シリアライゼーションの直前に呼び出される関数。関数の配列にすることもできます。onSend(request, reply, payload, done)
: レスポンスの送信直前に呼び出される関数。関数の配列にすることもできます。onResponse(request, reply, done)
: レスポンスが送信されたときに呼び出される関数。クライアントにさらにデータを送信することはできません。関数の配列にすることもできます。onTimeout(request, reply, done)
: リクエストのタイムアウトが発生し、HTTPソケットが切断されたときに呼び出される関数。onError(request, reply, error, done)
: ルートハンドラーによってエラーがスローまたはクライアントに送信されたときに呼び出される関数。handler(request, reply)
: このリクエストを処理する関数。Fastifyサーバーは、ハンドラーが呼び出されるときにthis
にバインドされます。注: アロー関数を使用すると、this
のバインドが解除されます。errorHandler(error, request, reply)
: リクエストのスコープに対するカスタムエラーハンドラー。グローバルなデフォルトエラーハンドラー、およびsetErrorHandler
で設定されたものを、ルートへのリクエストに対して上書きします。デフォルトのハンドラーにアクセスするには、instance.errorHandler
にアクセスできます。これは、プラグインが既に上書きしていない場合にのみ、fastifyのデフォルトのerrorHandler
を指していることに注意してください。childLoggerFactory(logger, binding, opts, rawReq)
: すべてのリクエストに対して子ロガーインスタンスを作成するために呼び出されるカスタムファクトリー関数。childLoggerFactory
の詳細を参照してください。setChildLoggerFactory
で設定されたものも含め、デフォルトのロガーファクトリーをルートへのリクエストに対して上書きします。デフォルトのファクトリーにアクセスするには、instance.childLoggerFactory
にアクセスできます。これは、プラグインが既に上書きしていない場合にのみ、FastifyのデフォルトのchildLoggerFactory
を指していることに注意してください。validatorCompiler({ schema, method, url, httpPart })
: リクエストのバリデーションのためのスキーマを構築する関数。バリデーションとシリアライゼーションドキュメントを参照してください。serializerCompiler({ { schema, method, url, httpStatus, contentType } })
: レスポンスのシリアライゼーションのためのスキーマを構築する関数。バリデーションとシリアライゼーションドキュメントを参照してください。schemaErrorFormatter(errors, dataVar)
: バリデーションコンパイラからのエラーをフォーマットする関数。バリデーションとシリアライゼーションドキュメントを参照してください。グローバルなスキーマエラーフォーマッターハンドラー、およびsetSchemaErrorFormatter
で設定されたものを、ルートへのリクエストに対して上書きします。bodyLimit
: デフォルトのJSON本文パーサーが、このバイト数よりも大きいリクエスト本文を解析するのを防ぎます。整数でなければなりません。fastify(options)
でFastifyインスタンスを最初に作成するときに、このオプションをグローバルに設定することもできます。デフォルトは1048576
(1 MiB)です。logLevel
: このルートのログレベルを設定します。下記を参照してください。logSerializers
: このルートのログにシリアライザーを設定します。config
: カスタム設定を格納するために使用されるオブジェクト。constraints
: リクエストのプロパティまたは値に基づいてルートの制限を定義し、find-my-wayの制約を使用してカスタマイズされたマッチングを有効にします。組み込みのversion
およびhost
制約を含み、カスタム制約戦略をサポートしています。prefixTrailingSlash
: プレフィックス付きルートとして/
を扱う方法を決定するために使用される文字列。both
(デフォルト):/prefix
と/prefix/
の両方を登録します。slash
:/prefix/
のみを登録します。no-slash
:/prefix
のみを登録します。
注: このオプションはサーバー設定の
ignoreTrailingSlash
を上書きしません。request
はリクエストで定義されています。reply
はレスポンスで定義されています。
注意: onRequest
、preParsing
、preValidation
、preHandler
、preSerialization
、onSend
、onResponse
のドキュメントは、フックでより詳細に説明されています。さらに、リクエストがhandler
によって処理される前にレスポンスを送信するには、フックからのリクエストへの応答を参照してください。
例
fastify.route({
method: 'GET',
url: '/',
schema: {
querystring: {
name: { type: 'string' },
excitement: { type: 'integer' }
},
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
},
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
})
簡略宣言
上記のルート宣言はよりHapiライクですが、Express/Restifyアプローチを好む場合は、それもサポートしています。
fastify.get(path, [options], handler)
fastify.head(path, [options], handler)
fastify.post(path, [options], handler)
fastify.put(path, [options], handler)
fastify.delete(path, [options], handler)
fastify.options(path, [options], handler)
fastify.patch(path, [options], handler)
例
const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
}
}
fastify.get('/', opts, (request, reply) => {
reply.send({ hello: 'world' })
})
fastify.all(path, [options], handler)
は、サポートされているすべてのメソッドに同じハンドラーを追加します。
ハンドラーはoptions
オブジェクト経由で指定することもできます。
const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
},
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
}
fastify.get('/', opts)
注: ハンドラーが
options
とショートカットメソッドの第3パラメーターの両方で指定されている場合、重複したhandler
エラーをスローします。
URL構築
Fastifyは静的URLと動的URLの両方をサポートしています。
パラメトリックなパスを登録するには、パラメーター名の前にコロンを使用します。ワイルドカードには、アスタリスクを使用します。静的ルートは常にパラメトリックルートとワイルドカードルートの前にチェックされます。
// parametric
fastify.get('/example/:userId', function (request, reply) {
// curl ${app-url}/example/12345
// userId === '12345'
const { userId } = request.params;
// your code here
})
fastify.get('/example/:userId/:secretToken', function (request, reply) {
// curl ${app-url}/example/12345/abc.zHi
// userId === '12345'
// secretToken === 'abc.zHi'
const { userId, secretToken } = request.params;
// your code here
})
// wildcard
fastify.get('/example/*', function (request, reply) {})
正規表現ルートもサポートされていますが、スラッシュをエスケープする必要があることに注意してください。正規表現はパフォーマンス面で非常にコストがかかることにも注意してください!
// parametric with regexp
fastify.get('/example/:file(^\\d+).png', function (request, reply) {
// curl ${app-url}/example/12345.png
// file === '12345'
const { file } = request.params;
// your code here
})
同じスラッシュのペア内(" / ")に複数のパラメーターを定義することができます。例えば
fastify.get('/example/near/:lat-:lng/radius/:r', function (request, reply) {
// curl ${app-url}/example/near/15°N-30°E/radius/20
// lat === "15°N"
// lng === "30°E"
// r ==="20"
const { lat, lng, r } = request.params;
// your code here
})
この場合、パラメーターセパレーターとしてダッシュ("-")を使用してください。
最後に、正規表現で複数のパラメーターを持つことができます。
fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', function (request, reply) {
// curl ${app-url}/example/at/08h24m
// hour === "08"
// minute === "24"
const { hour, minute } = request.params;
// your code here
})
この場合、パラメーターセパレーターとして、正規表現に一致しない任意の文字を使用できます。
最後のパラメーターは、パラメーター名の最後に疑問符("?")を追加することでオプションにすることができます。
fastify.get('/example/posts/:id?', function (request, reply) {
const { id } = request.params;
// your code here
})
この場合、/example/posts
と/example/posts/1
の両方をリクエストできます。オプションパラメーターは、指定されていない場合は未定義になります。
複数のパラメーターを持つルートはパフォーマンスに悪影響を与える可能性があるため、特にアプリケーションのホットパスにあるルートでは、可能な限り単一パラメーターのアプローチを優先してください。ルーティングの処理方法に興味がある場合は、find-my-wayを参照してください。
パラメーターを宣言せずにコロンを含むパスが必要な場合は、ダブルコロンを使用します。例えば
fastify.post('/name::verb') // will be interpreted as /name:verb
非同期/await
あなたはasync/await
ユーザーですか?大丈夫ですよ!
fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
return processed
})
ご覧のように、ユーザーにデータを送信するためにreply.send
を呼び出していません。本文を返すだけで完了です!
必要であれば、reply.send
を使用してユーザーにデータを返送することもできます。この場合、非同期ハンドラーで return reply
または await reply
を忘れずに行ってください。そうでないと、特定の状況で競合状態が発生する可能性があります。
fastify.get('/', options, async function (request, reply) {
var data = await getData()
var processed = await processData(data)
return reply.send(processed)
})
ルートがコールバックベースのAPIをラップしており、プロミスチェーンの外でreply.send()
を呼び出す場合、await reply
を使用できます。
fastify.get('/', options, async function (request, reply) {
setImmediate(() => {
reply.send({ hello: 'world' })
})
await reply
})
reply を返す方法も有効です。
fastify.get('/', options, async function (request, reply) {
setImmediate(() => {
reply.send({ hello: 'world' })
})
return reply
})
警告
return value
とreply.send(value)
を同時に使用する場合、先に実行された方が優先され、2番目の値は破棄されます。また、2回レスポンスを送信しようとしたため、警告ログも出力されます。- プロミスの外で
reply.send()
を呼び出すことは可能ですが、特別な注意が必要です。詳細はプロミス解決を参照してください。 undefined
を返すことはできません。詳細はプロミス解決を参照してください。
プロミス解決
ハンドラーが非同期関数であるか、プロミスを返す場合、コールバックとプロミスの制御フローをサポートするために必要な特別な動作を認識しておく必要があります。ハンドラーのプロミスが解決されると、明示的にハンドラー内でreply
をawaitまたはreturnしない限り、その値を使用してレスポンスが自動的に送信されます。
async/await
またはプロミスを使用したいが、reply.send
で値をレスポンスとして返したい場合- 必ず
return reply
/await reply
を行ってください。 reply.send
の呼び出しを忘れないでください。
- 必ず
async/await
またはプロミスを使用したい場合reply.send
を使用しないでください。- 送信したい値を返してください。
このようにして、最小限のトレードオフでコールバックスタイル
とasync-await
の両方をサポートできます。これだけ自由度が高いにも関わらず、エラー処理はアプリケーション内で一貫して処理する必要があるため、1つのスタイルのみを使用することを強くお勧めします。
注意:すべての非同期関数は、それ自体でプロミスを返します。
ルートプレフィックス
同じAPIの2つ以上の異なるバージョンを維持する必要がある場合があります。古典的なアプローチは、すべてのルートにAPIバージョン番号(例:/v1/user
)をプレフィックスとして付けることです。Fastifyは、すべてのルート名を手動で変更することなく、同じAPIの異なるバージョンを迅速かつスマートに作成するための方法、ルートプレフィックスを提供します。その仕組みを見てみましょう。
// server.js
const fastify = require('fastify')()
fastify.register(require('./routes/v1/users'), { prefix: '/v1' })
fastify.register(require('./routes/v2/users'), { prefix: '/v2' })
fastify.listen({ port: 3000 })
// routes/v1/users.js
module.exports = function (fastify, opts, done) {
fastify.get('/user', handler_v1)
done()
}
// routes/v2/users.js
module.exports = function (fastify, opts, done) {
fastify.get('/user', handler_v2)
done()
}
コンパイル時にプレフィックスが自動的に処理されるため(これはパフォーマンスにも全く影響しないことを意味します!)、同じ名前を2つの異なるルートで使用しても、Fastifyはエラーを報告しません。
これで、クライアントは次のルートにアクセスできるようになります。
/v1/user
/v2/user
これは必要な回数だけ実行でき、ネストされたregister
にも機能し、ルートパラメーターもサポートされています。
すべてのルートにプレフィックスを使用したい場合は、プラグイン内に配置できます。
const fastify = require('fastify')()
const route = {
method: 'POST',
url: '/login',
handler: () => {},
schema: {},
}
fastify.register(function (app, _, done) {
app.get('/users', () => {})
app.route(route)
done()
}, { prefix: '/v1' }) // global route prefix
await fastify.listen({ port: 3000 })
ルートプレフィックスとfastify-plugin
fastify-plugin
を使用してルートをラップする場合、このオプションは機能しません。プラグインをプラグインでラップすることで機能させることができます。例:
const fp = require('fastify-plugin')
const routes = require('./lib/routes')
module.exports = fp(async function (app, opts) {
app.register(routes, {
prefix: '/v1',
})
}, {
name: 'my-routes'
})
プレフィックス付きプラグイン内の/ルートの処理
/
ルートの動作は、プレフィックスが/
で終わるかどうかにより異なります。例として、プレフィックスが/something/
の場合、/
ルートを追加しても/something/
のみに一致します。プレフィックスが/something
の場合、/
ルートを追加すると/something
と/something/
の両方に一致します。
この動作を変更するには、上記のprefixTrailingSlash
ルートオプションを参照してください。
カスタムログレベル
ルートで異なるログレベルが必要になる場合があります。Fastifyでは、非常に簡単な方法でこれを実現できます。
プラグインオプションまたはルートオプションにlogLevel
オプションと必要な値を渡すだけです。
プラグインレベルでlogLevel
を設定すると、setNotFoundHandler
とsetErrorHandler
も影響を受けることに注意してください。
// server.js
const fastify = require('fastify')({ logger: true })
fastify.register(require('./routes/user'), { logLevel: 'warn' })
fastify.register(require('./routes/events'), { logLevel: 'debug' })
fastify.listen({ port: 3000 })
または、ルートに直接渡すこともできます。
fastify.get('/', { logLevel: 'warn' }, (request, reply) => {
reply.send({ hello: 'world' })
})
カスタムログレベルはルートのみに適用され、fastify.log
でアクセスできるグローバルなFastify Loggerには適用されないことに注意してください。
カスタムログシリアライザ
状況によっては、大きなオブジェクトをログに記録する必要がある場合がありますが、一部のルートではリソースの無駄になる可能性があります。この場合、カスタムserializers
を定義し、適切なコンテキストにアタッチできます!
const fastify = require('fastify')({ logger: true })
fastify.register(require('./routes/user'), {
logSerializers: {
user: (value) => `My serializer one - ${value.name}`
}
})
fastify.register(require('./routes/events'), {
logSerializers: {
user: (value) => `My serializer two - ${value.name} ${value.surname}`
}
})
fastify.listen({ port: 3000 })
コンテキストによってシリアライザを継承できます。
const fastify = Fastify({
logger: {
level: 'info',
serializers: {
user (req) {
return {
method: req.method,
url: req.url,
headers: req.headers,
host: req.host,
remoteAddress: req.ip,
remotePort: req.socket.remotePort
}
}
}
}
})
fastify.register(context1, {
logSerializers: {
user: value => `My serializer father - ${value}`
}
})
async function context1 (fastify, opts) {
fastify.get('/', (req, reply) => {
req.log.info({ user: 'call father serializer', key: 'another key' })
// shows: { user: 'My serializer father - call father serializer', key: 'another key' }
reply.send({})
})
}
fastify.listen({ port: 3000 })
設定
新しいハンドラーを登録する際に、設定オブジェクトを渡してハンドラー内で取得できます。
// server.js
const fastify = require('fastify')()
function handler (req, reply) {
reply.send(reply.routeOptions.config.output)
}
fastify.get('/en', { config: { output: 'hello world!' } }, handler)
fastify.get('/it', { config: { output: 'ciao mondo!' } }, handler)
fastify.listen({ port: 3000 })
制約
Fastifyは、Host
ヘッダーなどのリクエストのプロパティに基づいて、特定のリクエストのみに一致するようにルートを制約することをサポートしています。find-my-way
制約を介して他の値も使用できます。制約は、ルートオプションのconstraints
プロパティで指定します。Fastifyには、version
制約とhost
制約という2つの組み込み制約が用意されており、リクエストの他の部分を検査して、リクエストに対してルートを実行するかどうかを決定するために、独自の カスタム制約戦略を追加できます。
バージョン制約
ルートにconstraints
オプションでversion
キーを指定できます。バージョン付きルートを使用すると、同じHTTPルートパスに対して複数のハンドラーを宣言できます。その後、各リクエストのAccept-Version
ヘッダーに従って一致させます。Accept-Version
ヘッダー値はsemver仕様に従う必要があり、ルートは一致させるために正確なsemverバージョンで宣言する必要があります。
ルートにバージョンが設定されている場合、FastifyはリクエストのAccept-Version
ヘッダーが設定されていることを要求し、同じパスのバージョンなしのルートよりもバージョン付きのルートを優先します。現在、高度なバージョン範囲とプレリリースはサポートされていません。
この機能を使用すると、ルーターの全体的なパフォーマンスが低下することに注意してください。
fastify.route({
method: 'GET',
url: '/',
constraints: { version: '1.2.0' },
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
})
fastify.inject({
method: 'GET',
url: '/',
headers: {
'Accept-Version': '1.x' // it could also be '1.2.0' or '1.2.x'
}
}, (err, res) => {
// { hello: 'world' }
})
⚠ セキュリティに関する注意事項
バージョン付けの定義に使用している値(例:
'Accept-Version'
)を使用して、レスポンスにVary
ヘッダーを設定して、キャッシュポイズニング攻撃を防いでください。プロキシ/CDNの一部として設定することもできます。const append = require('vary').append
fastify.addHook('onSend', (req, reply, payload, done) => {
if (req.headers['accept-version']) { // or the custom header you are using
let value = reply.getHeader('Vary') || ''
const header = Array.isArray(value) ? value.join(', ') : String(value)
if ((value = append(header, 'Accept-Version'))) { // or the custom header you are using
reply.header('Vary', value)
}
}
done()
})
同じメジャーまたはマイナーで複数のバージョンを宣言する場合、Fastifyは常にAccept-Version
ヘッダー値と互換性のある最も高いバージョンを選択します。
リクエストにAccept-Version
ヘッダーがない場合、404エラーが返されます。
カスタムバージョンマッチングロジックを定義できます。これは、Fastifyサーバーインスタンスを作成する際のconstraints
設定を使用して実行できます。
ホスト制約
リクエストのHost
ヘッダーの特定の値のみに一致するようにルートを制限するために、constraints
ルートオプションにhost
キーを指定できます。host
制約値は、完全一致の場合は文字列として、任意のホスト一致の場合は正規表現として指定できます。
fastify.route({
method: 'GET',
url: '/',
constraints: { host: 'auth.fastify.dev' },
handler: function (request, reply) {
reply.send('hello world from auth.fastify.dev')
}
})
fastify.inject({
method: 'GET',
url: '/',
headers: {
'Host': 'example.com'
}
}, (err, res) => {
// 404 because the host doesn't match the constraint
})
fastify.inject({
method: 'GET',
url: '/',
headers: {
'Host': 'auth.fastify.dev'
}
}, (err, res) => {
// => 'hello world from auth.fastify.dev'
})
ワイルドカードサブドメイン(またはその他の任意のパターン)に一致するホストを制約することも、正規表現host
制約を指定することで可能です。
fastify.route({
method: 'GET',
url: '/',
constraints: { host: /.*\.fastify\.dev/ }, // will match any subdomain of fastify.dev
handler: function (request, reply) {
reply.send('hello world from ' + request.headers.host)
}
})
非同期カスタム制約
カスタム制約を提供でき、database
などの別のソースからconstraint
基準を取得できます。非同期カスタム制約の使用は、ルーターのパフォーマンスに影響を与えるため、最後の手段としてください。
function databaseOperation(field, done) {
done(null, field)
}
const secret = {
// strategy name for referencing in the route handler `constraints` options
name: 'secret',
// storage factory for storing routes in the find-my-way route tree
storage: function () {
let handlers = {}
return {
get: (type) => { return handlers[type] || null },
set: (type, store) => { handlers[type] = store }
}
},
// function to get the value of the constraint from each incoming request
deriveConstraint: (req, ctx, done) => {
databaseOperation(req.headers['secret'], done)
},
// optional flag marking if handlers without constraints can match requests that have a value for this constraint
mustMatchWhenDerived: true
}
⚠ セキュリティに関する注意事項
非同期制約と共に使用する場合、コールバック内でエラーを返すことは決して推奨されません。エラーを回避できない場合は、カスタム
frameworkErrors
ハンドラーを用意して処理することをお勧めします。そうでない場合、ルートの選択が中断されるか、攻撃者に機密情報が公開される可能性があります。const Fastify = require('fastify')
const fastify = Fastify({
frameworkErrors: function (err, res, res) {
if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) {
res.code(400)
return res.send("Invalid header provided")
} else {
res.send(err)
}
}
})