GraphQLはGitHubやFaceBookなども採用している新しい、APIの形式です。
特徴としては、以下の点が挙げられます。
RESTの形式の場合は、各リソースの操作ごとにエンドポイントがありましたが、GraphQLでは、単一のエンドポイントしかありません。
例えば、RESTの場合、Userというリソースにアクセスするには、
上記のように、複数のエンドポイントが存在していました。
GraphQLでは、「POST: /graphql」
この1つだけ定義し、必要なリソースはクエリで定義する形になります。
どのリソースから何を取得するかはAPIを利用する側がクエリで定義できます。
例えば、Userが、名前、住所、性別など情報を持っており、名前だけ欲しい場合は、下記のようなクエリで取得できます。
{ users { name } }
RailsでGraphQLの形式でAPIを実装するGemがあるのでこれを使用して実際にAPIを作成してみようと思います。
目標としては、単純なCRUDの操作ができるところまでを想定しています。
Gemfileに以下のGemを追加します。
gem "graphql"
実行コマンド rails g graphql:install 作成されたもの create app/graphql/types create app/graphql/types/.keep create app/graphql/app_schema.rb create app/graphql/types/base_object.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/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
インストールができたので、APIを作成していきます。
初めにスキーマの定義をするためのオブジェクトを作成します。
**実行コマンド
rails g graphql:object User name:String emails:[Email]
実行結果
Running via Spring preloader in process 95 create app/graphql/types/user_type.rb
実行コマンド
rails g graphql:object Email id:ID email:String
実行結果
Running via Spring preloader in process 164 create app/graphql/types/email_type.rb
作成したファイルにはリソースの属性を記載します。
ここでは、下記のように属性を定義しています。
app/graphql/types/user_type.rb
module Types class UserType < Types::BaseObject field :id, ID, null: false # add field :name, String, null: true field :emails, [Types::EmailType], null: true end end
app/graphql/types/email_type.rb
module Types class EmailType < Types::BaseObject field :id, ID, null: false # modify field :email, String, null: true end end
query_type.rbにエントリーポイントを定義します。
これで、Userというリソースにアクセスできるようになります。
app/graphql/types/query_type.rb
module Types class QueryType < Types::BaseObject # Add root-level fields here. # They will be entry points for queries on your schema. # First describe the field signature: field :user, UserType, null: true do description "Find a user by ID" argument :id, ID, required: true end # Then provide an implementation: def user(id:) User.find(id) end end end
エントリーポイントを作成したので、早速APIを実行してみます。
実行には、GraphQLのクライアントツールを使用します。
まずは、idが1のデータを取得してみます。
リクエスト
{ user(id: 1) { id name emails { id email } } }
レスポンス
{ "data": { "user": { "id": "1", "name": "taro tanaka", "emails": [ { "id": "1", "email": "test1@example.com" } ] } } }
次に、全Userの名前を取得してみます
リクエスト
{ users { name } }
レスポンス
{ "data": { "users": [ { "name": "taro tanaka" }, { "name": "yuki sato" } ] } }
MutationsはGraphQLでデータの変更(作成、更新、削除など)する際に使用する構文です。
実装としては、Mutationsを作成しなくてもデータの変更は実装することは可能ですが、作法としてデータの変更を行う場合は、
Mutationを使用するようです。
※(GETメソッドで、データの変更を行なわないようなものだと思います。)
mutations以下に作成、更新、削除用のmutationを作成します。
作成用のMutations
module Mutations module UserMutations class CreateUserMutation < BaseMutation include Common # define return fields field :message, String, null: true field :errors, [String], null: false # define arguments argument :name, String, required: true argument :emails_attributes, [Types::EmailsAttributes], required: false # define resolve method def resolve(**params) user_params = create_user_params(params) user = User.new(user_params) user.save create_response(user) end end end end
更新用のMutations
module Mutations module UserMutations class UpdateUserMutation < BaseMutation include Common # define return fields field :message, String, null: true field :errors, [String], null: false # define arguments argument :id, ID, required: true argument :name, String, required: true argument :emails_attributes, [Types::EmailsAttributes], required: false # define resolve method def resolve(**params) user_params = create_user_params(params) user = User.find(user_params[:id]) user.update(user_params) create_response(user) end end end end
削除用のMutations
module Mutations module UserMutations class DeleteUserMutation < BaseMutation # define return fields type String # define arguments argument :id, ID, required: true # define resolve method def resolve(id:) User.find(id).destroy 'delete ok' end end end end
次に、mutation_type.rbにエントリーポイントを追加します。
module Types class MutationType < Types::BaseObject field :update_user, mutation: Mutations::UserMutations::UpdateUserMutation field :create_user, mutation: Mutations::UserMutations::CreateUserMutation field :delete_user, mutation: Mutations::UserMutations::DeleteUserMutation end end
エントリーポイントを追加したので、データの作成、更新、削除のリクエストを実行してみます。
データの作成リクエスト
mutation { createUser(name: "testinsert", emailsAttributes: [{email: "test1_aaaaa"}, {email: "test2"}]) { message errors } }
レスポンス
{ "data": { "createUser": { "message": "ok", "errors": [] } } }
データの更新リクエスト
mutation { updateUser(id: 1, name: "test", emailsAttributes: [{id: 1, email: "test1_aaaaa"}, {email: "test2"}]) { message errors } }
レスポンス
{ "data": { "updateUser": { "message": "ok", "errors": [] } } }
データの削除リクエスト
mutation { deleteUser( id: 1 ) }
レスポンス
{ "data": { "deleteUser": "delete ok" } }