FastifyのPluginsの動作を確認して遊んだ

Fastify

Fastifyを触って遊んでる。スピードが速いらしいけど、そういうところは深く考えずに面白そうだから触ってみている。

https://fastify.dev/

TypeScript, ESM, PNPM, Biome

TypeScriptとESMで書こうと思って、簡単なプロジェクトを作った。パッケージマネージャにはPNPM、フォーマッター&リンターにはこの前のJSConfで知ったBiome(バイオーム)を使ってみてる。

https://github.com/bufferings/hello-fasitify-ts/tree/8265736961f7fccbbecbd09d29e5136ba81da8dc

Node.jsやPNPMなどのバージョンはこう

❯ node -v
v21.2.0

❯ pnpm -v
8.11.0

❯ pnpm tsc -v
Version 5.3.2

❯ pnpm biome -V
Version: 1.3.3

Biome

今日のメインはBiomeじゃないんだけど、ちょっと触ったのでメモだけ

https://biomejs.dev/

ちょこっとだけデフォルトの設定から変更を入れている。

  • .gitignore ファイルの対象を無視するために vcs 設定を入れた
  • フォーマッターのインデントをtab->spaceに変更。行の幅も80->120に変更
  • JS/TSのクォートにはシングルクォートを使用するように変更

biome.json

{
  "$schema": "https://biomejs.dev/schemas/1.3.3/schema.json",
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
  },
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 120
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single"
    }
  }
}

IntelliJプラグインもあるから入れてみてるけど、どういう機能があるのかはいまいちよく分かってない。WebStormのon saveでフォーマットがかかるようにしてみてて便利。

Hello Fastify!

てことで本題。Fastifyのハローワールドはこんな感じ

src/server.ts

import Fastify from 'fastify';

const fastify = Fastify({
  logger: true,
});

fastify.get('/', async (request, reply) => {
  return 'Hi!';
});

try {
  await fastify.listen({ port: 3000 });
} catch (err) {
  fastify.log.error(err);
  process.exit(1);
}

こんな感じで package.json にスクリプトを入れているので

  "scripts": {
    "build": "tsc",
    "dev": "node --no-warnings --loader ts-node/esm --watch src/server.ts"
  },

実行はこう

❯ pnpm dev

叩くとこうなる

❯ curl localhost:3000/
Hi!

--watch オプションをつけてるのでファイルの内容を変更したらサーバーが再起動されて便利。

Plugins

FastifyにはPluginsという仕組みがある。というか、全部Pluginとして動くって感じなのかな。

このPluginsの仕組みによって、簡単にファイルを分割して管理できる。

プラグインの登録には fastify.register 関数を使う。コールバック形式もあるけど、僕はasync/awaitが好きなので、そっちを使う。さっきのハローワールドのエンドポイントはregisterを使うとこんな感じで書ける。

await fastify.register(async (instance, opts) => {
  instance.get('/', async (request, reply) => {
    return 'Hi!';
  });
});

Fastifyのインスタンスとオプションをパラメータとして受け取る関数を渡す。ので、この部分を別のファイルに定義してインポートして渡すことができて便利。

インスタンスの階層

register を使って書き直したハローワールドのエンドポイントは、実際は最初の例とちょっと違う状態になっている。Fastifyのインスタンスは階層化されるのだ。最初の例だと、直接 fastify インスタンスに get を登録していたので、ルートインスタンスに定義されていた状態。

register を使って書き直した方で、そのプラグインに渡された instance は、元の fastify インスタンスの子になってる。

階層の確認

FastifyのインスタンスはDecoratorsという仕組みで拡張できる。このDecoratorsを使って、階層を確認してみる。

TypeScriptに型を伝えて

declare module 'fastify' {
  interface FastifyInstance {
    top: number;
    inner: number;
  }
}

トップレベルのインスタンスと、プラグインの中のインスタンスにそれぞれデコレーターを設定してみる。

fastify.decorate('top', 1);

fastify.get('/', async (request, reply) => {
  return { top: fastify.top, inner: fastify.inner };
});

await fastify.register(async (instance, opts) => {
  instance.decorate('inner', 2);
  instance.get('/inner', async (request, reply) => {
    return { top: instance.top, inner: instance.inner };
  });
});

結果はこう

❯ curl localhost:3000/
{"top":1}

❯ curl localhost:3000/inner
{"top":1,"inner":2}

つまり、親のインスタンスに設定されているものは子から見えるが、子のインスタンスに設定されているものは親からは見えない。

プラグインで書いたものは自分の子のインスタンスに閉じた状態になるので便利。

でも、プラグインから親のインスタンスに手をいれて、他のプラグインから使いたくなることってあるよね。DBのコネクションとか、そういうケースのための仕組みがある。

プラグインから親のインスタンスに設定する

fastify-plugin パッケージをインストールして、プラグインをそのパッケージの関数で囲むと、親のFastifyインスタンスに設定される。

...
import fp from 'fastify-plugin';

...

await fastify.register(
  // fpでラップする
  fp(async (instance, opts) => {
    instance.decorate('innerWithFp', 3);
    instance.get('/innerWithFp', async (request, reply) => {
      return { top: instance.top, inner: instance.inner, innerWithFp: instance.innerWithFp };
    });
  }),
);

そうすると親のインスタンスにアサインされるので、親のスコープから見える。

❯ curl localhost:3000/
{"top":1,"innerWithFp":3}

❯ curl localhost:3000/inner
{"top":1,"inner":2,"innerWithFp":3}

❯ curl localhost:3000/innerWithFp
{"top":1,"innerWithFp":3}

ちなみに、registerawait をつけておくと、前のプラグインで設定したものが、その次のプラグインから使用可能になる。

面白かった。気が向いたらFastifyのスキーマ定義でも触ってみて遊ぼうかな。

今日の最終的なコードはここ

https://github.com/bufferings/hello-fasitify-ts/blob/c6365943e987aa351397f7aeb58986e65d05aa09/src/server.ts