🐥

【NestJS】GraphQLサーバーの起動からエラーハンドリングをするまでをソースコードで追いかける

2023/10/03に公開

動機

NestJSでGraphQLサーバーを実装するにあたって、エラーハンドリングをGraphQLの仕様にしたがって適切に行いたかったが(extensionsにエラーを追加したかった)、そもそもNestJS上でのリゾルバのエラーがどのようにGraphQLのエラーとしてthrowされるのか知らなかった為。

前提

GraphQLサーバーの実装には@nestjs/graphqlのGraphQLModuleを使用します。HTTPプロバイダーとしてはデフォルトのexpressを使用する前提でコードを読んでいきますが、おおよその流れとしてはfastifyを使用する場合と同様です。

GraphQLサーバーの起動まで

GraphQLModule

NestJSでモジュールをimportすると、onModuleInitというメソッドが自動で実行されます。184行目で_graphQlAdapterクラスのstartメソッドが実行されています。
https://github.com/nestjs/graphql/blob/6b647176b2684580e1bb2ac2d0a3d3bb19d64561/packages/graphql/lib/graphql.module.ts#L184

ApolloDriver

この_graphQlAdapterクラスの正体はApolloDriverで、startメソッドを実行すると、36行目でregisterServerが実行されます。
https://github.com/nestjs/graphql/blob/6b647176b2684580e1bb2ac2d0a3d3bb19d64561/packages/apollo/lib/drivers/apollo.driver.ts#L23-L51

registerServer

registerServerでは、HTTPプロバイダとしてexpressを使っているか、fastifyを使っているかを判別して、処理を分けています。expressを使用してるので、58行目のregisterExpressが実行されます。
https://github.com/nestjs/graphql/blob/6b647176b2684580e1bb2ac2d0a3d3bb19d64561/packages/apollo/lib/drivers/apollo.driver.ts#L53-L64

registerExpress

expressでapolloを使用する場合、expressMiddlewareというメソッドを使用して、初期化したapolloサーバーをミドルウェア化する必要があるので、registerExpressの中ではその処理を行っています。
https://github.com/nestjs/graphql/blob/6b647176b2684580e1bb2ac2d0a3d3bb19d64561/packages/apollo/lib/drivers/apollo-base.driver.ts#L113-L147

Apolloサーバーによるリゾルバのエラーのハンドリング

expressMiddleware

長いの抜粋ですが、ここでGraphQLのクエリを含んだHTTPリクエストをハンドリングしているようです。109行目でexecuteHTTPGraphQLRequestのエラーをキャッチして次のミドルウェアに渡しています。
https://github.com/apollographql/apollo-server/blob/1849cc9e9983fe46820a07d2ad100f84eb4db0e9/packages/server/src/express4/index.ts#L80-L109

executeHTTPGraphQLRequest

executeHTTPGraphQLRequestはrunPotentiallyBatchedHttpQueryというメソッドの結果を返しています。ここからは実際のクエリの解決になり複雑になってくるので追いかけませんが、このメソッドの中で、GraphQLエラーがthrowされるようです。
https://github.com/apollographql/apollo-server/blob/1849cc9e9983fe46820a07d2ad100f84eb4db0e9/packages/server/src/ApolloServer.ts#L1083-L1089

結局、エラーにextensionsを追加するにはどうすればいいのか

エラーの正規化のメソッドのコードを読むと、基本的にGraphQLエラーはそのままthrowできるようになっているみたいなので、下記のようにgraphqlパッケージのGraphQLエラーをそのまま使うのが良さそうかなと思いました。

GraphQLError
      throw new GraphQLError('user aleady exists', {
        extensions: {
          userErrorMessage: '該当のユーザーは既に存在します。',
        },
      });

https://github.com/apollographql/apollo-server/blob/1849cc9e9983fe46820a07d2ad100f84eb4db0e9/packages/server/src/errorNormalize.ts#L88-L105

Coludus テックブログ

Discussion