一応 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が定義されます。
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を定義
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 リクエストメッセージボディクエリを記述します。
別のクエリでGraphQLにデータ要求してみましょう。
id 1のbookのtitleだけを取得します。
複数のクエリを一度に実行してみましょう。
すべてのbookのidと、id 1のbookの全ての情報を取得してみます。
このように、クエリがどんなデータを返却してくれるのかが直感的にわかりますね!
さらに、必要なデータだけに絞って取得することもできたので過剰に無駄なデータ取得することも無くなります。
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
というファイルが作成されているのがわかります。
以下の様に、変更してみます。
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を実行
ところで、以下の様にデータを受け取りたい場合はどうするでしょうか?
{
"data": {
"createBook": {
"title": "GraphQLな奴",
"body": "GraphQL理解しないとGraphql-rubyわからんね"
}
}
}
返却されるデータの形はfieldで定義されていました。
なので返却されるfieldを変えてしまえばいいわけです。
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
実際にクエリするとこんな感じ
コードの流れをざっくり説明していきます。
-
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だけ指定しているのでそれ以外のデータは返ってきませんね。
結構色々定義して面倒だと思われた方!!!
仮にですが、input型の指定無しでクエリ実行したい! という方は GraphQL::Schema::RelayClassicMutation
を継承させないようにすればOKです。
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の指定無しでクエリの実行ができました。
しかし、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ライフを٩(ˊωˋ*)و✧*