概要
GraphQL Rubyのバージョン1.8~以降で導入されたClass-basedの書き方を使用し、簡単なAPIを作成する方法を紹介します。
ネット上でClass-basedの書き方に関する記事が少なかったので、誰かの助けになれば幸いです。
GraphQL,GraphQL Rubyの基本概念等は以下で学習して頂けます。
- GraphQL入門 - 使いたくなるGraphQL - Qiita
- GraphQL Rubyに関して
- GraphQL公式
- How to GraphQL - The Fullstack Tutorial for GraphQL
GraphQL with Ruby on Rails
ユーザーが記事(post)の取得、作成、更新、削除を行える簡単なapiをRuby on RailsとGraphQLを用いて作成します。
ソースコードはこちらから確認する事ができます。
Installation
まず、以下のコマンドでapiモードでrailsアプリケーションを作成します。
$ rails new blog --api
Gemfileにgraphqlとgraphql-rail を加えます。
gem 'graphql'
gem 'graphiql-rails' #開発環境でgraphiqlを使用可能にします。こちらは必須ではありません。
通常のrailsアプリならこれだけでセットアップは完了ですが、apiモードとして作成する場合は、config/application.rbに以下の文を加えます。
(開発環境でgraphiqlを使用する可能にする為です。こちらが必要ない場合は下記を追加する必要はありません。)
require "sprockets/railtie"
必要なgem等を追加したら、以下のコマンドを流しましょう。
2つめのコマンドを流すとapp/graphqlディレクトリが作成され、GraphQL関連のファイルはこのディレクトリ下に配置されます。
$ bundle install
$ rails generate graphql:install
Queries
大雑把に説明するとGraphQLでは全てがType(オブジェクトの設計書のようなもの)を持っており、そのTypeに従い、Queriesを使用して、データの取得を行い、Mutationsを使用して、データの作成・更新を行います。
まず、データの取得を行うQueriesからみていきましょう。
Postモデルを作成し、 rake db:migrateを流します。
$ rails g model Post title:string description:text
$ rake db:migrate
Queryでデータを取得できる事を確認するため、 rails consoleを使用して事前にデータを作成しましょう。
$ rails c
$ Post.create(title: "What is Ruby?", description:"Ruby is a programming language")
$ Post.create(title: "How to learn Ruby", description:"Read some books brah")
GraphQLでは各Typeを基にQueryの実行を行なっていきます。
そこで、PostのTypeを定義するため、以下のコマンドを流しPost Typeを作成します。
$ rails g graphql:object Post id:ID! title:String! description:String!
以下が上記のコマンドで作成されたファイルの中身です。
module Types
class PostType < Types::BaseObject
field :id, ID, null: false
field :title, String, null: false
field :description, String, null: false
end
end
class~endを使用してよりSytaxが以前と比べてよりわかりやすくなっています。
Class Based Syntaxについて詳しく知りたい方は公式でわかりやすく説明されています。
Query Resolver
Typeは定義されましたが、サーバはこのタイプをどのように扱えば良いかわかりません。
そこで、実際にqueriesを実行するためにresolverを作成していきます。
先ほどrails g graphql:installを流した際に作成された app/graphql/types/query_type.rbに以下のコードを追加する事で、全てのPostの取得と特定のidを持つPostの取得ができます。
# app/graphql/types/query_type.rb
module Types
class QueryType < Types::BaseObject
field :posts, [Types::PostType], null: false
def posts
Post.all
end
field :post, Types::PostType, null: false do
argument :id, Int, required: false
end
def post(id:)
Post.find(id)
end
end
end
GraphiQLで確認
以下をroutes.rbに追加します。
Rails.application.routes.draw do
if Rails.env.development?
# add the url of your end-point to graphql_path.
mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
end
post '/graphql', to: 'graphql#execute'
end
上記のif~endを加える事でhttp://localhost:3000/graphiqlにアクセスし、実際に先ほど定義したqueryの確認を行う事ができます。
実際に rails sをコンソールで流し、http://localhost:3000/graphiqlで以下のquery を実行してみましょう。
# 全てのPost取得
{
posts {
id
title
description
}
}
############## 結果 #####################
{
"data": {
"posts": [
{
"id": 1,
"title": "title1",
"description": "description1"
},
{
"id": 2,
"title": "title2",
"description": "description2"
},
{
"id": 3,
"title": "title3",
"description": "description3"
},
]
}
}
# 特定のPostの取得
{
post(id:1) {
id
title
description
}
}
############## 結果 #####################
{
"data": {
"post": {
"id": 1,
"title": "title1",
"description": "description1"
}
}
}
Mutations
上記で簡単に説明したようにGraphQLではMutationsを使用してデータの作成・更新を行います。
以下のapp/graphql/types/mutation_type.rbも最初にrails g graphql:installを流した際に作成されています。
module Types
class MutationType < Types::BaseObject
# TODO: remove me
field :test_field, String, null: false,
description: "An example field added by the generator"
def test_field
"Hello World"
end
end
end
Mutation Create
Postを作成する為に使用されるmutationを作成する為に以下のコマンドを流します。
$ rails g graphql:mutation CreatePost
上記のコマンドは以下の2つの事を行います。
- (1)
graphql/mutations/create_post.rbの作成。 - (2)
field :createPost, mutation: Mutations::CreatePostをgraphql/types/mutations_type.rbに追記する。
module Mutations
class CreatePost < GraphQL::Schema::RelayClassicMutation
# TODO: define return fields
# field :post, Types::PostType, null: false
# TODO: define arguments
# argument :name, String, required: true
# TODO: define resolve method
# def resolve(name:)
# { post: ... }
# end
end
end
# app/graphql/types/mutation_type.rb
module Types
class MutationType < Types::BaseObject
field :createPost, mutation: Mutations::CreatePost
end
end
graphql/mutations/create_post.rb に書かれている TODOs に従い、fieldやargumentを定義して、Postの作成が行えるよう、以下のように編集していきます。
:graphql/mutations/create_post.rb
module Mutations
class CreatePost < GraphQL::Schema::RelayClassicMutation
graphql_name 'CreatePost'
field :post, Types::PostType, null: true
field :result, Boolean, null: true
argument :title, String, required: false
argument :description, String, required: false
def resolve(**args)
post = Post.create(title: args[:title], description: args[:description])
{
post: post,
result: post.errors.blank?
}
end
end
end
GraphiQLで確認
先ほどと同じくrails sでサーバを立ち上げ GraphQL でPostの作成ができるか試してみましょう。
mutation {
createPost(
input:{
title: "title1"
description: "description1"
}
){
post {
id
title
description
}
}
}
############## 結果 #####################
{
"data": {
"createPost": {
"post": {
"id": 15,
"title": "title1",
"description": "description1"
}
}
}
}
Mutation Update
Postを更新する為に使用されるmutationを作成する為に以下のコマンドを流します
$ rails g graphql:mutation UpdatePost
自動作成された /graphql/mutations/update_post.rb を以下のように変更しましょう。
/graphql/mutations/create_post.rbと非常に似ており、違いはargumentにてidを指定する箇所くらいです。
module Mutations
class UpdatePost < GraphQL::Schema::RelayClassicMutation
graphql_name 'UpdatePost'
field :post, Types::PostType, null: true
field :result, Boolean, null: true
argument :id, ID, required: true
argument :title, String, required: false
argument :description, String, required: false
def resolve(**args)
post = Post.find(args[:id])
post.update(title: args[:title], description: args[:description])
{
post: post,
result: post.errors.blank?
}
end
end
end
GraphiQLで確認
rails sでサーバを立ち上げ GraphQL でPostの更新ができるか試してみましょう。
mutation {
updatePost(
input:{
id: 1
title: "Updated"
description: "UPdated"
}
){
post {
id
title
description
}
}
}
############## 結果 #####################
{
"data": {
"updatePost": {
"post": {
"id": 1,
"title": "Updated",
"description": "UPdated"
}
}
}
}
Mutation Delete
作成、更新のmutationsを作成したのとほぼ同じ工程で削除の為のmutationを作成します。
以下のコマンドを流します。
$ rails g graphql:mutation DeletePost
今回は削除の為に指定するidのみをargumentにセットします。
module Mutations
class DeletePost < GraphQL::Schema::RelayClassicMutation
graphql_name 'DeletePost'
field :post, Types::PostType, null: true
field :result, Boolean, null: true
argument :id, ID, required: true
def resolve(**args)
post = Post.find(args[:id])
post.destroy
{
post: post,
result: post.errors.blank?
}
end
end
end
GraphiQLで確認
rails sでサーバを立ち上げ GraphQL でPostの削除ができるか試してみましょう。
mutation {
deletePost(
input:{
id: 1
}
){
post {
id
title
description
}
}
}
############## 結果 #####################
{
"data": {
"posts": [
{
"id": 2,
"title": "How to learn Ruby"
},
{
"id": 3,
"title": "title1"
}]
}
}
Connection fields(1)
GraphQLではtypes,queries,mutationsの働きがわかればアソーシエーションも簡単に対処できます。
実際に各ポストが持つコメントを取得できるようにしてみましょう。
まず、Commentモデルを作成し、Postと 多対1(has_many/belongs_to)の関係をもたせます。
また、ここで確認の為のデータを作成しておきましょう。
$ rails g model Comment content:string post:references
$ rake db:migrate
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :post
end
# app/models/post.rb
class Post < ApplicationRecord
has_many :comments, dependent: :destroy
end
Post.new.tap do |post|
post.title = 'title'
post.description = 'description'
post.comments.build(content: 'comment1')
post.save!
end
$ rake db:seed
次に、以下のコマンドでComment typeを作成しましょう。
$ rails g graphql:object Comment id:ID! content:String!
上記のコマンドは以下のファイルを作成します。
module Types
class CommentType < Types::BaseObject
description 'Comment'
field :id, ID, null: false
field :content, String, null: false
end
end
そして、commentsというfieldをPost typeに追加しましょう。
module Types
class PostType < Types::BaseObject
description 'Post'
field :id, Int, null: false
field :title, String, null: false
field :description, String, null: false
field :comments, [Types::CommentType], null: false
end
end
GraphiQLで確認
rails sでサーバを立ち上げ GraphQL でPostのcommentsが取得できるか試してみましょう。
{
posts {
id
title
comments {
id
content
}
}
}
############## 結果 #####################
{
"data": {
"posts": [
{
"id": "1",
"title": "title",
"comments": [
{
"id": "1",
"content": "comment1"
}
]
}
]
}
}
Connection fields(2)
Commentを作成できるようにしてみましょう。
以下のコマンドを流して/graphql/types/mutation_type.rbを作成します。
$ rails g graphql:mutation CreateComment
次のfield :post, Types::PostType, null: falseをapp/graphql/types/comment_type.rbに追加しましょう。
module Types
class CommentType < Types::BaseObject
description 'Comment'
field :id, ID, null: false
field :content, String, null: false
field :post, Types::PostType, null: false
end
end
そして、 app/graphql/mutations/create_comment.rbを以下のように変更します。
module Mutations
class CreateComment < GraphQL::Schema::RelayClassicMutation
graphql_name 'CreateComment'
field :comment, Types::CommentType, null: true
field :result, Boolean, null: true
argument :post_id, ID, required: true
argument :content, String, required: true
def resolve(**args)
post = Post.find(args[:post_id])
comment = post.comments.build(content: args[:content])
comment.save
{
comment: comment,
result: post.errors.blank?
}
end
end
end
GraphiQLで確認
rails sでサーバを立ち上げ GraphQL でPostのcommentsが作成できるか試してみましょう。
mutation {
createComment(
input:{
postId: 1
content: "NEW COMMENT"
}
){
comment {
id
content
post {
id
title
comments {
id
content
}
}
}
}
}
############## 結果 #####################
{
"data": {
"createComment": {
"comment": {
"id": "2",
"content": "NEW COMMENT",
"post": {
"id": "1",
"title": "title",
"comments": [
{
"id": "1",
"content": "comment1"
},
{
"id": "2",
"content": "NEW COMMENT"
}
]
}
}
}
}
}
Commentの更新や削除を行いたい場合はPostの更新、削除の方法を真似て作成してみてください。
参考
- GraphQL Ruby(Class-based API) - DEV Community 👩💻👨💻
- Building a GraphQL Server with Ruby Backend Tutorial
- A Guide to GraphQL in Plain English – freeCodeCamp.org
- GraphQL Ruby
- How to Implement a GraphQL API in Rails - via @codeship | via @codeship
- When you use graphiql-rails for your api only rails application
- When you use graphiql-rails for your api only rails application