NestJSでBFFをつくった話

MF KESSAIでバックエンドのエンジニアをやっているgarsueです。 先日当社のnoteのインタビュー企画NestJSを使ったBackend For Frontend(以下BFF)について「ブログお待ちしてます!」と言われてしまったので、重い腰を上げて書くことにしました。

当社ではNestJSをGraphQLをしゃべるBFFとして使っているので、そのあたりの勘所や知見を少し紹介します。

なぜBFFとしてNestJSを採用したか

まず、フロントエンドのフレームワークはNuxtJSで決まっていました。当社での2年以上の実績もあり、もはや定番となっています。 また、NuxtJSと合わせてGraphQLを使うのも定番となっており、今回もGraphQLを使うことが決まっていました。

NuxtJSから呼び出すBFFは今までGoでgqlgenを使ったGraphQLサーバーとして実装していました。 これはこれで悪くないのですが、やりたいことはバックエンドのサービス群を呼び出してフロントエンドの都合のいい形に加工して返すことぐらいなので、もっと軽く開発できる何かにしたいなとも思っていました。

そこで高い抽象度で記述量も少なく抑えられ、フロントエンドエンジニアも触りやすいTypeScript製のフレームワークであるNestJSを採用しました。 GraphQLもサポートされていて、NestJS自体がマイクロサービスアーキテクチャを前提にしてるフレームワークでもあり、今回の要件にマッチしていました。

NestJS + GraphQL

NestJSによるGraphQLサーバーの作り方はNestJSの公式ドキュメントにまとまっているため詳しくはそちらを参照してください。

ここでは実際に開発してみて得られた知見について書いていきます。

Resolver

GraphQLサーバーはクエリに対するResolverの集まりとして実装されます。

ここで大事なことは、ResolverはControllerではないということです。

NestJSの標準機能であるFilterやGuardはControllerに対して使用することが前提になっています。 Resolverではこれらを直接使えない場合があります。

この点についての具体的な対処方法は明示的にドキュメントに記載されてはいませんが、Guardを認証状態の判定に使うテクニックの一部としてGraphQLのResolverへの適用方法が記述されています。このあたりを真似るとよいでしょう。

基本的にはControllerとのインターフェイスの差異を吸収するように実装を調整していくことになります。

フィールドに対するInterceptor

Resolverが返す各フィールドについて値を取得するロジック(フィールドResolver)を@ResolveFieldデコレータをつけたメソッドとして実装できます。

ただし、フィールドResolverに対してInterceptorは実行されません。 当社ではエラーレポーティングでInterceptorを使用していたのですが、これだとフィールドResolver内で発生したエラーを拾えないため困ります。

こういったときにはfieldResolverEnhancersが使えます。 これはフィールドResolverにも適用したい機能('interceptors''guards'など)を指定できます。

これで要件は満たせるのですが、ドキュメントにもあるとおりフィールドResolver単位でInterceptorなどが都度実行されるため、パフォーマンスは下がります。 利用は必要最小限にとどめつつ、重い処理が何度も実行されないように十分注意して使う必要があります。

おわりに

NestJSを実際に使ってみて得られた知見は他にもありますが、ひとまずこれくらいで勘弁してください🙇
国内ではまだこれからのフレームワークですが、利用シーンにうまくハマれば普及していく可能性も感じています。

NestJSについて語りたい方はぜひお気軽にMF KESSAIへ遊びにきてください!