LoginSignup
2
3

More than 1 year has passed since last update.

RailsでGraphQLサーバー構築体験🤔💭

Last updated at Posted at 2021-12-30

一応 RailsでWeb APIを作成する方法とメリット🤔💭 の記事の続きですが前回の記事を読んでいなくても構築可能です。

RailsでGraphQLサーバーを立ち上げて基本的なQuery、Mutation機能を実装してみました!

前提

  • GraphQLをある程度知っていないとコードの理解は困難です。m(_ _)m

  • GraphQLが何なのかは説明しません。ある程度、GraphQLがどういうものかを知っている前提で進めます。

ぜひ公式サイトなどで仕様をご参照ください。

  • HTTP リクエストメソッドについては説明しません。

MDNをぜひ (https://developer.mozilla.org/ja/docs/Web/HTTP/Methods)

  • HTTPリクエストボディの説明はしていません。

「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
このサイト初心者向けに用語説明しててわかりやすいです。

  • DBにbooksテーブルがある状態で始めます。

先ほどのstringのtitle, textのbodyカラムを持ったテーブルです。

RailsをGraphQLサーバーにするには?

今回は、まずRailsをAPIモードを用意します。(前回の記事で用意しています。)

完成版ソースコードもあります。

その後、Gem graphql-ruby をGemfileに記載してbundle installしてGraphQLサーバー化していきます。

APIサーバーは既に用意しているのでGemをインストールするところから進めます。

RailsでGraphQLサーバーを作成していきます

Gemfileで以下を追記し、installします。

gem 'graphql'
$ bundle install

GraphQLのテンプレートを生成します。以下のコマンドを実行するとGraphQL関連のファイル群が生成されます。

$ rails g graphql:install

Running via Spring preloader in process 35807
      create  app/graphql/types
      create  app/graphql/types/.keep
      create  app/graphql/graphql_server_sample_schema.rb
      create  app/graphql/types/base_object.rb
      create  app/graphql/types/base_argument.rb
      create  app/graphql/types/base_field.rb
      create  app/graphql/types/base_enum.rb
      create  app/graphql/types/base_input_object.rb
      create  app/graphql/types/base_interface.rb
      create  app/graphql/types/base_scalar.rb
      create  app/graphql/types/base_union.rb
      create  app/graphql/types/query_type.rb
add_root_type  query
      create  app/graphql/mutations
      create  app/graphql/mutations/.keep
      create  app/graphql/mutations/base_mutation.rb
      create  app/graphql/types/mutation_type.rb
add_root_type  mutation
      create  app/controllers/graphql_controller.rb
       route  post "/graphql", to: "graphql#execute"
Skipped graphiql, as this rails project is API only
  You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app
      create  app/graphql/types/node_type.rb
      insert  app/graphql/types/query_type.rb
      create  app/graphql/types/base_connection.rb
      create  app/graphql/types/base_edge.rb
      insert  app/graphql/types/base_object.rb
      insert  app/graphql/types/base_object.rb
      insert  app/graphql/types/base_union.rb
      insert  app/graphql/types/base_union.rb
      insert  app/graphql/types/base_interface.rb
      insert  app/graphql/types/base_interface.rb
      insert  app/graphql/graphql_server_sample_schema.rb

Object typeを定義

Object typeはどういう形のデータをreturnするかを決めるものです。

$ rails g graphql:object Book
Running via Spring preloader in process 97118
      create  app/graphql/types/book_type.rb

app/graphql/types/book_type.rb というファイルが作成されます。

ファイルを開くと以下の様にfieldが定義されているはずです。

*Bookモデルを作成していると、自動でfieldが定義されます。

app/graphql/types/book_type.rb
module Types
  class BookType < Types::BaseObject
    field :id, ID, null: false
    field :title, String, null: true
    field :body, String, null: true
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end

Queryを定義

app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    # bookのデータがあれば[本の情報、本の情報, ...]という形式でデータを返します。
    # 角括弧内でnull: trueしているので空の[ ]を返してもOKにしてます。
    # null: falsede必ずデータは何かしら返さなきゃいけないようにしています。(なかったらエラー)
    field(:books, [BookType, null: true], null: false) do
      description 'fetch all books'
    end

    # bookのデータを一つ返すようにしています。
    # null: falseなので必ずデータ返さなきゃいけないようにしてます。(なかったらエラー)
    field(:book, BookType, null: false) do
      description 'find a book by id'
      argument :id, ID, required: true
    end

    def books
      Book.all
    end

    def book(id:)
      Book.find(id)
    end
  end
end

これで実際にデータを取得する準備が整いました。実際にAPIの開発ツールを使って検証してみましょう。

Gem graphiql-railsを使ってGraphiqlなどでも確認できますが個人的にPostmanが好きなのでPostmanで確認します。

PostmanはAPI開発の手助けをしてくれるツールです。

URIを指定して通信リクエストを実行してどういうデータが返ってくるかなぁ〜とか色々確認ができるわけです。

早速、GraphQLのQueryを指定してデータが取得できるかどうがみてみましょう。

Queryを実行

  • rails s でサーバーを起動します。

  • HTTP verbを POSTにします。

  • GraphQLのラジをボタンを選択します。

  • HTTP リクエストメッセージボディクエリを記述します。

スクリーンショット 2021-12-29 20.42.58.png

別のクエリでGraphQLにデータ要求してみましょう。

id 1のbookのtitleだけを取得します。

スクリーンショット 2021-12-29 20.44.53.png

複数のクエリを一度に実行してみましょう。
すべてのbookのidと、id 1のbookの全ての情報を取得してみます。

スクリーンショット 2021-12-30 17.15.36.png

このように、クエリがどんなデータを返却してくれるのかが直感的にわかりますね!

さらに、必要なデータだけに絞って取得することもできたので過剰に無駄なデータ取得することも無くなります。

Mutationを定義

先ほどはデータの取得はQueryで行いましたが、データの作成、更新、削除はMutationというもので行います。

データを作成して、取得するという機能を制作していきます。

今回はbookのcreate(作成用)のmutationを定義していきます。

以下を実行して、create_bookというmutationを実装していきます。

$ rails g graphql:mutation create_book

Running via Spring preloader in process 61114
       exist  app/graphql/mutations
   identical  app/graphql/mutations/.keep
   identical  app/graphql/mutations/base_mutation.rb
   identical  app/graphql/types/mutation_type.rb
add_root_type  mutation
      create  app/graphql/mutations/create_book.rb
        gsub  app/graphql/types/mutation_type.rb

app/graphql/mutations/create_book.rb というファイルが作成されているのがわかります。

以下の様に、変更してみます。

app/graphql/mutations/create_book.rb
module Mutations
  class CreateBook < BaseMutation
    # 返却されるデータの形を定義しています。
    field :book, Types::BookType, null: false

    # GraphQL Variableの情報を定義してます。
    # これがないと作成時の情報も利用できないです。
    argument :body, String, required: true
    argument :title, String, required: true

    # Resolverで返却したいデータをreturnします。
    def resolve(**attributes)
      # attributesには次のHashが渡ってきます。(attributesという名前以外でもOKです。)
      # {:body=>"GraphQL理解しないとGraphql-rubyわからんね", :title=>"GraphQLな奴"}
      book = Book.create!(attributes)
      { book: book } # このデータが返却されます。(Rubyはreturn省略できます。)
    end
  end
end

field :book, Types::BookType, null: falseと定義していますがこれは最終的に
以下の様なデータが返ってきます。

{
    "data": {
        "createBook": {
            book: {
              "title": "GraphQLな奴",
              "body": "GraphQL理解しないとGraphql-rubyわからんね"
            }
        }
    }
}

Mutationを実行

スクリーンショット 2021-12-30 1.34.13.png

ところで、以下の様にデータを受け取りたい場合はどうするでしょうか?

{
    "data": {
        "createBook": {
            "title": "GraphQLな奴",
            "body": "GraphQL理解しないとGraphql-rubyわからんね"
        }
    }
}

返却されるデータの形はfieldで定義されていました。

なので返却されるfieldを変えてしまえばいいわけです。

app/graphql/mutations/create_book.rb
module Mutations
  class CreateBook < BaseMutation
    # 既にapp/graphql/types/book_type.rbで定義しているfield群を利用します。
    # その場合はfieldではなくてtypeで呼び出します。
    type Types::BookType

    argument :body, String, required: true
    argument :title, String, required: true

    def resolve(**attributes)
      Book.create!(attributes)
    end
  end
end

実際にクエリするとこんな感じ

スクリーンショット 2021-12-30 1.46.46.png

コードの流れをざっくり説明していきます。

  • Mutationが GraphQL::Schema::RelayClassicMutation を継承してると、デフォルトでInput objectが自動で定義されるようになっています。クエリにもinput型の定義が必要になります。(GraphQL Rubyの仕様です。)

  • input型を定義して、それに相当するデータもGraphQL Variableに指定してクエリを実行します。

input型はストロングパラメーターの型があるパージョンと言えばRails触ってる人はわかりやすいかな...? 🤔💭

  • fieldで定義した通りのデータがResolverから(resolveメソッドから)返ってきます。

最終的にデータを返すメソッドがresolveです。Resolverとか呼ばれてる奴です。

  • 今回はbookのtitleとbodyだけを取得するようにしているので、title, bodyの情報が返ってきました。

クエリ実行時のfieldには、title, bodyだけ指定しているのでそれ以外のデータは返ってきませんね。

スクリーンショット 2021-12-30 2.24.49 2.png

結構色々定義して面倒だと思われた方!!!

仮にですが、input型の指定無しでクエリ実行したい! という方は GraphQL::Schema::RelayClassicMutation を継承させないようにすればOKです。

app/graphql/mutations/create_book.rb
module Mutations
  # 継承元を変更します。
  class CreateBook < GraphQL::Schema::Mutation
    field :book, Types::BookType, null: false

    argument :title, String, required: true
    argument :body, String, required: true

    def resolve(**attributes)
      book = Book.create!(attributes)
      { book: book }
    end
  end
end

この状態でクエリを実行すると以下のようにinput型の指定無し、GraphQL Variableの指定無しでクエリの実行ができました。

スクリーンショット 2021-12-30 16.42.14.png

しかし、input型がある方がストロングパラメーターみたいに使いまわせたり、型の面で便利なので、本当はinput型を使っていた方がいいと思います。

GraphQLの何がよいの?🤔💭

これまでQuery、Mutationでデータの取得、作成を行ってきました。

  • GraphQLのいいところはやはり直感的にどういうデータが返ってくるかが分かりやすい。

    • データの型も指定できるのでどういう型のデータが来るかも分かりやすいです。
  • 本当に必要な必要最小限のデータだけを柔軟に取得できる

    • クエリ実行時にbookのidだけ必要であればクエリのfieldにidだけ書けば良いのです。
  • 一つのエンドポイントで、かつ一度のリクエストで色々なデータにアクセス可能

    • この記事では全データの取得、データの一つを取得、データの作成をたった一つのエンドポイントで成し遂げました。
    • http://localhost:3000/graphql だけでやりとげました。たくさんRouting定義しなくてもいいわけです。
    • クエリは一度に複数指定も可能でした。

他にも色々メリットがありますがそんなAPIサーバーが作成をしていきました。

フロントエンドからどうやってクエリ実行すれば良いの?

Apollo ClientとかGraphQLに特化したライブラリを使うと良いです。

ライブラリを使わなくともPOSTメソッドなHTTPリクエストのボディにクエリを持たせているだけですのでFetch APIやAxiosを使ってリクエストボディにクエリを含ませれば実行できます。

要は、HTTPリクエストができればクエリは実行できるわけです。(なんならHTTPじゃなくてもできるらしい)

Apollo Clientもまとめてみたいのですが記事の範疇を超えるので割愛します。m(_ _)m

素敵なAPI、GraphQLライフを٩(ˊωˋ)و✧

2
3
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
3