• トップ
  • ブログ一覧
  • Ruby on Rails&GraphQLのエラーレスポンス
  • Ruby on Rails&GraphQLのエラーレスポンス

    りゅうちゃん(エンジニア)りゅうちゃん(エンジニア)
    2022.11.07

    IT技術

    はじめに

    前回の記事の続きになります!

    Ruby on Rails & GraphQLの環境構築と実装2022.07.13Ruby on Rails & GraphQLの環境構築と実装はじめにRailsとGraphQLで開発しているプロジェクトに参画したのですが、環境構築を1からしたことがなかったので...

    GraphQLにおけるエラーレスポンス

    一般的なHTTP通信のエラーレスポンスでは、ステータスコードで判断しますが、基本的にGraphQLからは200か500しか返せません。

    なので200のレスポンスの中でerrorsフィールドを返すというのがベストプラクティスとされているようです。

    1{
    2  "errors": [
    3    {
    4      "message": "エラー内容",
    5      "locations": [...],
    6      "path": [...],
    7      "extensions": {
    8        "code": "NOT_FOUND",
    9        "timestamp": "..."
    10      }
    11    }
    12  ]
    13}

    【参考】
    https://spec.graphql.org/October2021/#sec-Errors

    Ruby on Rails & GraphQLにおけるエラーレスポンスの実装

    ではRails上ではどうするのかというと、GraphQL::ExecutionErrorという例外を発生させることでベストプラクティスな実装をすることができます。

    【参考】
    https://graphql-ruby.org/errors/error_handling.html

    前回の記事で実装したUser APIにエラーレスポンスを組み込んでみましょう!

    特定User取得のエラーレスポンスの現状

    まず最初に現状を確認します。

    rails s コマンドでサーバーを起動し、graphiqlにアクセスし、登録されていないidでリクエストしてみましょう。

    errorsはレスポンスされましたが、backtraceがそのまま出ちゃっていますね。

    Postmanで確認してみると、500エラーになっていることがわかります。

    GraphqlControllerの中にprotect_from_forgery を入れないと、422エラーになるので注意

    これはUser Modelで発生したActiveRecord::RecordNotFoundエラーがそのまま伝播し、500エラーになっています。

    なのでrescueコードを用いて、代わりにGraphQL::ExecutionErrorを伝播させましょう!

    特定User取得のエラーレスポンスの実装

    リクエストパラメータにあるidがUsersテーブル上に存在しない場合のエラーレスポンスを実装します。

    1module Queries
    2  module Resolvers
    3    class User < GraphQL::Schema::Resolver
    4      type Types::UserType, null: false
    5      description "特定Userの取得"
    6
    7      argument :id, String, required: true, description: "Userのid"
    8
    9      def resolve(params)
    10        begin
    11          ::User.find(params[:id])  
    12        rescue ActiveRecord::RecordNotFound => exception
    13          raise GraphQL::ExecutionError, "id:#{params[:id]} not found"
    14        end
    15      end
    16    end
    17  end
    18end

    User.findメソッドをbegin-rescueで囲って、GraphQL::ExecutionErrorをraiseしているだけですね。

    早速実行結果を見ていきましょう!

    コード上で指定したメッセージで表示されていて、backtraceも消えていますね。

    Postmanで見ると、ステータスコードが200になっていることがわかります。

    ただし、現状メッセージだけでしかエラーが判別できません。

    ベストプラクティスにもあるように、errors内にextensions:codeで、ステータスコードのように大まかなエラー内容が区別できるようにしましょう!

    エラーレスポンスにエラーコードを追加

    まずはエラーコードを管理するファイルを作成します。

    今回はUserが見つからないエラーなので、NOT_FOUNDのコードを実装します。

    1module Error
    2  class GraphqlError
    3    class << self
    4      def codes
    5        {
    6          not_found: "NOT_FOUND",
    7        }
    8      end
    9    end
    10  end
    11end

    私のプロジェクトではこれをlibフォルダに追加したので、config/application.rbに以下の変更を入れました。

    1module TestGraphqlApi
    2  class Application < Rails::Application
    3    # Initialize configuration defaults for originally generated Rails version.
    4    config.load_defaults 7.0
    5    config.paths.add 'lib', eager_load: true # ここを追加
    6
    7    ...
    8end

    さきほど実装したGraphQL::ExecutionErrorに代入しましょう。

    1def resolve(params)
    2  begin
    3    ::User.find(params[:id])  
    4  rescue ActiveRecord::RecordNotFound => exception
    5    raise GraphQL::ExecutionError.new(
    6      "id:#{params[:id]} not found",
    7      extensions: {
    8        code: Error::GraphqlError.codes[:not_found]
    9      }
    10    )
    11  end
    12end

    こちらは単純に、GraphQL::ExecutionErrorの初期化時にextensions引数を用いてるだけですね。

    これでエラーコードをレスポンスに代入することができました!

    まとめ

    HTTPステータスコードが指定できないので、若干ややこしい仕様になっていますが、エラーレスポンスを実装することができました。

    GraphQLを学び始めたときに実装を悩んだ箇所だったので、参考になりましたらうれしいです!

    今回のコードも以下のリポジトリにまとめてありますので、ぜひ確認してみてください。

    https://github.com/ryuto-imai/test_graphql_api

    ※前回の記事時点のコードは以下のタグに残してあります

    https://github.com/ryuto-imai/test_graphql_api/tree/ruby-on-rails-graphql-environment-implementation-syain

    りゅうちゃん(エンジニア)

    りゅうちゃん(エンジニア)

    おすすめ記事