LoginSignup
2
2

railsでのGraphQLサーバーの構築と周辺ツールのセットアップ手順

Posted at

やる事

railsアプリケーションでGraphQLを開発するための初期構築と周辺ツールのセットアップを行います。

  1. graphql gem のインストール
  2. graphql-rubyの初期化コマンド実行
  3. AppSchemaクラスの設定項目の変更
  4. rubocop-graphqlのセットアップ
  5. スキーマ情報をファイル出力するrake task追加

1. graphql gem のインストール

GraphQLサーバーの構築にgraphqlgemを使います

Gemfile
+ gem 'graphql'
bundle install

2. graphqlの初期化コマンド実行

graphqlgemの機能でGraphQL開発のセットアップを行うコマンドgraphql:installコマンドがrails generatorとして追加されているので使用します。

rails g graphql:install --skip-graphiql --skip_keeps --api=true --relay=false --skip_mutation_root_type=true

ここでは以下のオプションを指定しています。

  • graphiqlは使用しないので--skip-graphiqlを指定
  • 空ディレクトリを維持する.keepファイルは不要なので--skip_keepsを指定
  • apiモードでの実装にするので--apiオプションをtrueに指定
  • relayは使用しないので--relayオプションをfalseに指定
  • mutationは今回使わないのでskip_mutation_root_typeオプションをtrueに指定

生成ファイル

app/graphqlディレクトリの作成などが行われます

$ tree app/graphql/

app/graphql/
├── app_schema.rb
└── types
    ├── base_argument.rb
    ├── base_enum.rb
    ├── base_field.rb
    ├── base_input_object.rb
    ├── base_interface.rb
    ├── base_object.rb
    ├── base_scalar.rb
    ├── base_union.rb
    └── query_type.rb

graphqlのエンドポイントの定義とコントローラーの雛形も作成されます。

config/routes.rb
+ post "/graphql", to: "graphql#execute"
app/controllers/graphql_controller.rb
class GraphqlController < ApplicationController
  # If accessing from outside this domain, nullify the session
  # This allows for outside API access while preventing CSRF attacks,
  # but you'll have to authenticate your user separately
  # protect_from_forgery with: :null_session

  def execute
    variables = prepare_variables(params[:variables])
    query = params[:query]
    operation_name = params[:operationName]
    context = {
      # Query context goes here, for example:
      # current_user: current_user,
    }
    result = AppSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
    render json: result
  rescue StandardError => e
    raise e unless Rails.env.development?
    handle_error_in_development(e)
  end

  private

  # Handle variables in form data, JSON body, or a blank value
  def prepare_variables(variables_param)
    case variables_param
    when String
      if variables_param.present?
        JSON.parse(variables_param) || {}
      else
        {}
      end
    when Hash
      variables_param
    when ActionController::Parameters
      variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables.
    when nil
      {}
    else
      raise ArgumentError, "Unexpected parameter: #{variables_param}"
    end
  end

  def handle_error_in_development(e)
    logger.error e.message
    logger.error e.backtrace.join("\n")

    render json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: 500
  end
end

3. AppSchemaクラスの設定項目の変更

自動生成で作成したapp/graphql/app_schema.rbに設定の追加、不要箇所の削除を行います。

app/graphql/app_schema.rb
class AppSchema < GraphQL::Schema
+  disable_introspection_entry_points unless Rails.env.development?
+  
   query(QueryType)
+
+  max_complexity 200
+  max_depth 30
+  default_page_size 50
+  
-  # For batch-loading (see https://graphql-ruby.org/dataloader/overview.html)
-  use GraphQL::Dataloader
-
-  # GraphQL-Ruby calls this when something goes wrong while running a query:
-  def self.type_error(err, context)
-    # if err.is_a?(GraphQL::InvalidNullError)
-    #   # report to your bug tracker here
-    #   return nil
-    # end
-    super
-  end
-
-  # Union and Interface Resolution
-  def self.resolve_type(abstract_type, obj, ctx)
-    # TODO: Implement this method
-    # to return the correct GraphQL object type for `obj`
-    raise(GraphQL::RequiredImplementationMissingError)
-  end
-
-  # Stop validating when it encounters this many errors:
  validate_max_errors(100)
end

変更後のファイルの内容は以下の通りです。

app/graphql/app_schema.rb
class AppSchema < GraphQL::Schema
  disable_introspection_entry_points unless Rails.env.development?

  query(QueryType)
  
  max_complexity 200
  max_depth 30
  default_page_size 50
  validate_max_errors(100)
end

これらの設定を適切に行う事で、高負荷なクエリの抑制や、不要な情報の出力を防ぎ堅牢なサーバーに繋がります。
逆に、設定不足や不適切な値の設定をしてしまうことで脆弱性やサービスの安定性にもつながるのでサービスに応じて適切に設定を行うことが必要です。

設定した値の詳細

disable_introspection_entry_points

GraphQLスキーマ情報をIntorospectionとして取得可能にするのは開発環境のみで良いので、開発環境以外の場合いはdisable_introspection_entry_pointsで無効化しています

max_complexity

GeaphQLはクエリが柔軟な分、複雑なクエリの構築が行えてしまいます。そのためクエリの複雑具合を検出して一定の閾値を設け抑制する設定をmax_complexityとして追加します。

  • 同様に、リソースをネストとして無限にデータ検索を行う事も可能なのでネストに制限を加えるmax_depthを指定します

default_page_size

リソースの件数が大きくなることを想定しページングのサイズのデフォルトをdefault_page_sizeで指定しています

validate_max_errors

不正な入力が行われ複数のバリデーションエラーが発生する際にエラー数に制限を設定しバリデーションを中断する設定をvalidate_max_errorsとして設定します

4. rubocop-graphqlのセットアップ

Gemfileに定義を追加しインストールします。

Gemfile
+ gem "rubocop-graphql"
bundle install

rubocopの設定ファイルでrubocop-graphqlを読み込むように追記します。

.rubocop.yml
require:
+  rubocop-graphql

rubocopを実行し警告が出る場合は修正していきます

rubocop

特定のクラスでdiasbleを指定する

以下の二つのクラスでdescription定義の強制が求められますが以下のclassでは定義メソッドが存在しないのでdiasbleします。

  • GraphQL::Schema::Field
  • GraphQL::Schema::Argument
app/graphql/types/base_argument.rb
- class BaseArgument < GraphQL::Schema::Argument
+ class BaseArgument < GraphQL::Schema::Argument # rubocop:disable GraphQL/ObjectDescription NOTE: `description`は`GraphQL::Schema::Argument`にはない
end
app/graphql/types/base_field.rb
- class BaseField < GraphQL::Schema::Field
+ class BaseField < GraphQL::Schema::Field # rubocop:disable GraphQL/ObjectDescription NOTE: `description`は`GraphQL::Schema::Field`にはない
end

5. スキーマ情報をファイル出力するrake task追加

フロントエンド開発などで活用可能するためにGraphQLスキーマ情報をファイル出力するrake task追加します。
graphql-rubygemにはrails taskを追加するGraphQL::RakeTaskクラスが用意されているので、こちらを使用します。

ソースコードはこちら

rake taskの定義

lib/tasks配下にgraphql_schema_dump.rakeを作成して以下の様に定義します。

lib/tasks/graphql_schema_dump.rake
require 'graphql/rake_task'

load_context = Proc do
  current_user = User.first

  {
    current_user:,
  }
end

GraphQL::RakeTask.new(schema_name: 'AppSchema', load_context:, directory: 'graphql_schema')
  • GraphQL::RakeTaskクラスをnewする事でrails taskが追加されます。
  • schema_nameはアプリケーションで用いているスキーマクラスの名前AppSchema(app/graphql/app_schema.rb)を指定します。
  • ログイン情報等のコンテキストをスキーマの中で扱っている場合はload_contextを使ってダミーデータをコンテキストに引き渡すProcを指定する必要があります。
    • ここではDBに存在するUserオブジェクトを取得して引き渡しています。
  • ファイルを出力するディレクトリをgraphql_schemaに指定します

rake taskの確認

上記を追加すると以下の様にスキーマ出力にrake taskが追加されています。

rails task -T | grep graphql

rails graphql:pro:validate[gem_version]                                            # Get the checksum of a graphql-pro version and compare it to published versions on GitHub and graphql-ruby.org
rails graphql:schema:dump                                                          # Dump the schema to JSON and IDL
rails graphql:schema:idl                                                           # Dump the schema to IDL in graphql_schema/schema.graphql
rails graphql:schema:json                                                          # Dump the schema to JSON in graphql_schema/schema.json

追加されたrake taskの詳細

rails graphql:pro:validate[gem_version]

  • Proバージョンを使用している場合にライセンスが有効化の検証をする

rails graphql:schema:dump

  • スキーマをIDLJSON形式両方でそれぞれ出力する

rails graphql:schema:idl

  • スキーマをIDL形式で出力する

rails graphql:schema:json

  • スキーマをJSON形式で出力する

参考

開発環境

  • graphql (2.1.0)
  • rails 7.0.8
  • ruby 3.2.2p53
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2