GraphQLとエラー境界を持つReactのカスタムエラーページ

GitHubのすばらしい500エラーページ

この記事が気に入ったら、GitHubプルリクエストの自動リマインダーをチームに送信するSlackボットであるプルリマインダーをチェックしてください。

GraphQLとReactの使用中に最近遭遇した1つの課題は、エラーの処理方法でした。開発者として、サーバーレンダリングされたアプリケーションにデフォルトの500、404、および403ページを実装した可能性がありますが、ReactとGraphQLでこれを行う方法を理解するのは難しいです。

この投稿では、チームがこの問題にどのようにアプローチしたか、実装した最終的なソリューション、GraphQL仕様からの興味深い教訓について説明します。

バックグラウンド

私が取り組んでいたプロジェクトは、GraphQL、Apollo Client、およびexpress-graphQLを使用してReactで構築されたかなり典型的なCRUDアプリでした。ユーザーに標準エラーページを表示することで、特定の種類のエラー(たとえば、サーバーのダウンなど)を処理したかったのです。

最初の課題は、エラーをクライアントに伝えるための最良の方法を見つけることでした。 GraphQLは、500、400、403などのHTTPステータスコードを使用しません。代わりに、応答にはエラーの配列に問題のリストが含まれます(エラーについてはGraphQL仕様を参照してください)。

たとえば、サーバーで何かが壊れたときのGraphQLレスポンスは次のようになります。

GraphQLエラーレスポンスはHTTPステータスコード200を返すため、エラーの種類を識別する唯一の方法は、エラー配列を調べることでした。エラーメッセージプロパティにサーバーでスローされた例外が含まれていたため、これは不適切なアプローチのように思われました。 GraphQL仕様では、メッセージの値は開発者向けであると規定されていますが、値が人間が読めるメッセージであるか、プログラムで処理されるように設計されたものであるかは指定されていません。

すべてのエラーには、エラーを理解および修正するためのガイドとして開発者向けのエラーの文字列の説明を含むキーメッセージのエントリが含まれている必要があります。

GraphQL応答へのエラーコードの追加

これを解決するために、標準化されたエラーコードをエラーオブジェクトに追加しました。これは、クライアントがプログラムでエラーを識別するために使用できます。これは、StripeのREST APIが人間が読み取れるメッセージに加えて文字列エラーコードを返す方法に触発されました。

開始する3つのエラーコードを決定しました:authentication_error、resource_not_found、およびserver_error。

これらをGraphQLレスポンスに追加するために、独自のformatError関数をexpress-graphqlに渡し、サーバーでスローされた例外をレスポンスに追加される標準コードにマッピングします。 GraphQLの仕様では、一般に、エラーオブジェクトにプロパティを追加することは推奨されていませんが、拡張オブジェクトにこれらのエントリをネストすることで許可しています。

その後、GraphQLの応答エラーを簡単に分類できました。

express-graphqlによって生成された応答にコードを追加する独自の方法を開発しましたが、apollo-serverは同様の組み込み動作を提供するようです。

React Error Boundariesを使用したエラーページのレンダリング

サーバーでエラーを処理する適切な方法を見つけたら、クライアントに注意を向けました。

デフォルトでは、server_error、authorization_error、またはauthorization_not_foundが発生したときに、アプリにグローバルエラーページ(たとえば、「エラーが発生しました」というメッセージが表示されたページ)を表示するようにしました。ただし、必要に応じて、特定のコンポーネントのエラーを処理できる柔軟性も必要でした。

たとえば、ユーザーが検索バーに何かを入力しているときに何かがおかしくなった場合、エラーページにフラッシュオーバーするのではなく、コンテキスト内でエラーメッセージを表示したいと考えました。

これを実現するために、最初にapollo-clientのQueryおよびMutationコンポーネントとそれらの子の間に配置されるGraphqlErrorHandlerというコンポーネントを作成しました。このコンポーネントは、応答でエラーコードをチェックし、関心のあるコードを識別した場合に例外をスローしました。

GraphqlErrorHandlerを使用するために、apollo-clientのQueryおよびMutationコンポーネントをラップしました。

次に、機能コンポーネントは、react-apolloに直接アクセスする代わりに、独自のクエリコンポーネントを使用しました。

サーバーがエラーを返したときにReactアプリが例外をスローしていたので、これらの例外を処理し、適切な動作にマップしたいと考えました。

以前の目標は、デフォルトでグローバルエラーページ(たとえば、「エラーが発生しました」というメッセージが表示されたページ)を表示することでしたが、必要に応じてコンポーネント内でエラーをローカルで処理できる柔軟性があることを思い出してください。

Reactエラー境界は、これを行うための素晴らしい方法を提供します。エラー境界は、子コンポーネントツリーの任意の場所でJavaScriptエラーをキャッチできるReactコンポーネントであるため、カスタム動作でそれらを処理できます。

サーバー関連の例外をキャッチし、適切なエラーページを表示するGraphqlErrorBoundaryというエラー境界を作成しました。

エラー境界は、アプリのすべてのコンポーネントのラッパーとして使用します。

エラーページをレンダリングするのではなく、コンポーネントのエラーを処理する場合は、コンポーネントツリーのより深い場所でエラー境界を使用できます。

たとえば、以前のコンポーネントでカスタムエラー処理動作が必要な場合の外観は次のとおりです。

要約

GraphQLはまだ比較的新しいものであり、エラー処理は開発者が直面していると思われる一般的な課題です。 GraphQL応答で標準化されたエラーコードを使用することにより、便利で直感的な方法でエラーをクライアントに伝えることができます。 Reactアプリでは、エラーの境界はアプリのエラー処理動作を標準化する優れた方法を提供し、必要なときに柔軟性を維持します。