以下の内容はhttps://let.blog.jp/tag/Fastifyより取得しました。


Fastify で POST サイズの上限を無制限にできない
POST するときのボディのサイズの話
nginx も Fastify もデフォルトは 1MB なので 10MB まで許可にしたいときに両方を変更しないといけないです
二重に管理するのは無駄ですし 片方だけしか更新してなかったみたいな設定漏れが出るようにしか思わないです

なので外側の nginx で制御して Node.js 側は無制限にしようとしました
ですが Fastify ではサイズを無制限にできないようです

サーバー全体やルート単位で bodyLimit を設定できますが 0 以下や Number.isInteger が false になるものは不正な設定として弾かれます
なので 1 以上の整数にしかできず これと必ず比較するので無制限にならないです
0 は無制限というありそうな設定はないですし Infinity も設定できません
Number.MAX_SAFE_INTEGER を設定しておけばいいといえばいいのですが なんかしっくりこないです
リバースプロキシを配置することを推奨するなら こういうところを快適にしてほしいものです

nginx を置かずに使ってると Node.js 側での制限が必須なので特になんとも思わなかったですが nginx を置いてみて気付いた不満点です

直接自分で Fastify インスタンスを作るところならまだいいですが Fastify CLI を使うとなると コマンドラインオプションで bodyLimit を指定することになります
コマンドラインで Number.MAX_SAFE_INTEGER 的なものを指定って結構面倒です
完全に一致しなくてもいいので 適当に 1TB などにしておくにしても 0 の数が多くなると見づらいです
Fastify CLI で外部からプラグインを参照できない
Fastify CLI を使うと app.js というルートプラグインが各プラグインを autoload する作りになっています
app.js はこういうの

https://github.com/fastify/fastify-cli/blob/v6.1.1/templates/app-esm/app.js
import path from 'path'
import AutoLoad from '@fastify/autoload'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

// Pass --options via CLI arguments in command to enable these options.
export const options = {}

export default async function (fastify, opts) {
// Place here your custom code!

// Do not touch the following lines

// This loads all plugins defined in plugins
// those should be support plugins that are reused
// through your application
fastify.register(AutoLoad, {
dir: path.join(__dirname, 'plugins'),
options: Object.assign({}, opts)
})

// This loads all plugins defined in routes
// define your routes in one of these
fastify.register(AutoLoad, {
dir: path.join(__dirname, 'routes'),
options: Object.assign({}, opts)
})
}

基本は Fastify CLI から使われるのみですが この app.js をプラグインとして使うこともできます
主にテストなどで使われます
https://github.com/fastify/fastify-cli/blob/v6.1.1/templates/app-esm/test/helper.js

テスト以外でも app のインスタンスを作ってそこからグローバルのプラグインで追加したメソッドを使うと便利かなと思ったのですが

import helper from "fastify-cli/helper.js"

const app = await helper.build([path_to_app_js], {})

app.foo()

これで foo が見つからないエラーでした
foo は plugins フォルダ内のプラグインで decorate しています

原因は Fastify のインスタンスに app.js のプラグインを register するときにカプセル化を有効にしていることでした
カプセル化されると app.js の内部では参照できますが 外側からは参照できません

app.js のデフォルトでは上のコードの通り プラグインの関数は fastify-plugin でラップされていません
ここはあまりいじることを想定されてないみたいですし ラップしないのが推奨なようです
実際 他のアプリと混ぜて同じサーバーで動かしたいときなどでは プラグイン全体をカプセル化して特定のプレフィックス以下に配置したいユースケースはありそうですし ラップしないのは正しい気がします
ですが 今回みたいにラップして 外側から参照できるようにカプセル化しないようにしたい場合もあります

となると app.js を使う側の Fastify CLI で fastify-plugin を通してほしいです
オプションで切り替えれるのかなと探したのですが そういうのはないようでした
https://github.com/fastify/fastify-cli/blob/v6.1.1/start.js#L149

現状だと仕方ないので app.js 側で fastify-plugin を通すしかなさそうです
他アプリと混ぜて使う想定がないなら常に fastify-plugin を通しても問題なさそうですが 環境変数を見て変えるということもできそうです

[app.js]
// 前略

const App = async function (fastify, opts) {
// 略
}

if (!process.env.ENCAPSULATE_APP) {
App = fp(App)
}

export default App
Fastify の printPlugins で表示される名前
Fastify では printRoutes や printPlugins で登録したルートやプラグインをわかりやすく表示できます
printPlugins ではプラグインの名前が表示されます

import Fastify from "fastify"
import sensible from "@fastify/sensible"

const fastify = Fastify()

await fastify.register(sensible)

console.log(fastify.printPlugins())
root -1 ms
├── bound _after 8 ms
├── @fastify/sensible 1 ms
└── bound _after 0 ms

ただ register のところで import を使うと問題が出ます

import Fastify from "fastify"

const fastify = Fastify()

await fastify.register(import("@fastify/sensible"))

console.log(fastify.printPlugins())
root -1 ms
├── bound _after 6 ms
├── [object Promise] 1 ms
└── bound _after 1 ms

import が返すのは Promise オブジェクトなので [object Promise] という表示になってました
register の引数のところで import を書くのは Fastify 側でサポートされていて ドキュメントでも見かける方法なのに対応できてないのは残念です

ただ import が終わって Promise が解決されないと中身の情報がわからないです
printPlugins が呼び出されるときに Promise が解決済みとも限らないですし仕方ないものなのかもしれません

名前をつけてみたら動くかなと試したら動きました

import Fastify from "fastify"

const importWithName = (specifier, name) =>
Object.assign(import(specifier), { name: name || specifier })

const fastify = Fastify()

await fastify.register(importWithName("@fastify/sensible", "sensible"))

console.log(fastify.printPlugins())
root -1 ms
├── bound _after 8 ms
├── sensible 1 ms
└── bound _after 0 ms

考えてみたら .name を参照してますが Promise だと .name はないはずです
どうやって作ってるのか気になったのでソースを見てみました

https://github.com/fastify/avvio/blob/v8.3.0/lib/get-plugin-name.js

いくつかパターンがあるみたいです
Promise のプロパティを追加しなくても単純にオプションとして name を渡すで良さそうです

import Fastify from "fastify"

const fastify = Fastify()

await fastify.register(import("@fastify/sensible"), { name: "sensible" })

console.log(fastify.printPlugins())
root -1 ms
├── bound _after 4 ms
├── sensible 1 ms
└── bound _after 0 ms

デバッグなど開発時向け機能の printPlugins のためだけに名前をつける必要があるのかはわかりませんけど 一時的にどれなのかわからないからつけるというときには良さそうです
Fastify のセッション
久々に Fastify を使ったらセッションってどのプラグイン使えばいいのだっけとなったので
https://fastify.dev/ecosystem

公式のプラグインだと 3 種類

@fastify/cookie
@fastify/session
@fastify/secure-session

@fastify/cookie はセッションというよりは cookie を扱うライブラリ
だけど署名機能があるので koa の koa-session 相当のものならこれで十分
ただし あくまで cookie ライブラリなのでセッション風に特定のオブジェクトを更新するだけで後は自動でやってくれるということはない
cookie を読み取って署名チェックや cookie をレスポンスで送信するなどの制御は自分で実装する必要あり
それらを行うための関数は用意されるので addHook を使って onRequest や onSend にフックを追加すれば良い

@fastify/session はサーバーサイドのセッション
PHP みたいなもの
ID を cookie に書き込んでデータはサーバー側で持つ
デフォルトだとセッションデータはメモリ上に保持するのでセッション数が増えるとメモリを使うしサーバーのリスタートで消える
ストアの実装をカスタマイズできるのでちゃんとしたところで使うならファイル等に書き込んだほうがいい
redis に保存するなら一応コミュニティに別のプラグインがある
ただ スターがほとんどなくてマイナーみたいなのでストアを自作でもいいかも
このプラグインを使うのに @fastify/cookie が必須
内部で自動でやってくれないので自分でインストールと register しないといけない

@fastify/secure-session はクライアントサイドでデータを持つセッション
@fastify/cookie の署名とは違って暗号化してくれるのでユーザーが中身を見れない
hapi の iron 的なもの
暗号化には sodium-native を使ってる
sodium でググってると高速な暗号化というのを見かけたけど Node.js 組み込みの crypto と比べてどっちが速いのかはわからない
わざわざ外部のものを使ってるんだから組み込みよりは高速?
事前に付属ツールで鍵を発行しておいて プラグインの登録時にその鍵を渡す必要あり
鍵を使わずに secret にパスワード指定でも動かせるけど 起動時に毎回内部で鍵を生成してるので少し遅くなるらしい
内部で @fastify/cookie を使ってるけど @fastify/session とは違ってインストールも register も自動でやってくれるので意識しなくて良い
Fastify 4 の変更が思ってたのと違った
少し前に Fastify の 4 が出たってニュースを見かけて簡単な変更点紹介で ルートのハンドラー内でのレスポンスの返し方が統一されたみたいのを聞いてました
Fastify のルートハンドラーではコールバック関数と async 関数どっちでも使えるようになってることもあって 以前使ったときは罠のような仕様があって不便に感じたので あれが改善されたんだなーと思ってました

一般的なレスポンスの返し方はこの 2 パターンです

fastify.get("/foo", (request, reply) => {
reply.send({ hello: "world" })
})

fastify.get("/bar", async (request, reply) => {
return { hello: "world" }
})

通常の関数ならコールバック関数で async 関数なら return でレスポンスを返します
async 関数でできるならと通常の関数でも return でレスポンスを返したくなります
しかしこれは無視されるようで reply を使ったコールバック関数を呼び出さないとレスポンスは返されずブラウザはずっとロード中になります
他フレームワークだとだいたいデフォルトのレスポンスを返してくれて Node.js を生で使ったときみたいにレスポンスを返さず放置はなかったのでこれが Fastify の不満点の一つでした

return 値をレスポンスとして捉えてしまうとあとから reply 関数を呼び出す場合に対応できなくなるので return は無視する仕組みなんだと理解しましたが それでも async 関数の場合は return できるので使いづらく感じます
それがあってから全部 async 関数にしようとしてますが それでも async 関数になってなかったということはけっこうあります

そういう問題があって きっと 4 での変更点はこれが改善されたんだろうなと思って試してみました

fastify.get("/", (request, reply) => {
return { hello: "world" }
})

でレスポンスが返ってきました
改善されてるーと喜んで記事にでも書こうかと思って ついでに 4 の変更点をまとめた公式ブログの発表を軽く見ていると……これのことじゃなさそう
async 関数のときに あとからコールバック関数を使うなら reply を return しないといけないとか書いてる

じゃあもっと前から直ってたの?
と思って 3 系をいくつか試したところ どれも return でレスポンスを返せていました
そんな前から対応されてたんだ
まぁ あとから reply を呼び出すつもりなら return が undefined になりそうだし 十分判断はできるのかな
やっぱり気になったので Github で履歴見てみると 3.0 の更新で変更されてました

その他の 4 での変更点はあまり興味ない部分でしたし 今回はメジャーアップデートの割にそんなに大きく変わってないようです



以上の内容はhttps://let.blog.jp/tag/Fastifyより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14