Log目次

【Rails】GraphQLを用いたAPIの実装

作成日 2019-07-07更新日 2019-07-07

GraphQLの特徴

GraphQLはGitHubやFaceBookなども採用している新しい、APIの形式です。

特徴としては、以下の点が挙げられます。


エンドポイントが1つしかない

RESTの形式の場合は、各リソースの操作ごとにエンドポイントがありましたが、GraphQLでは、単一のエンドポイントしかありません。

例えば、RESTの場合、Userというリソースにアクセスするには、


上記のように、複数のエンドポイントが存在していました。

GraphQLでは、「POST: /graphql」

この1つだけ定義し、必要なリソースはクエリで定義する形になります。

必要な情報のみをAPIで取得できる

どのリソースから何を取得するかは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

graphqlのオブジェクト追加

インストールができたので、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の実行

エントリーポイントを作成したので、早速APIを実行してみます。

実行には、GraphQLのクライアントツールを使用します。

DLリンク

まずは、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の追加

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" } }

参考