GraphQL Ruby(Class-based Syntax)で簡単なAPIを作成


概要

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 RailsGraphQLを用いて作成します。

ソースコードはこちらから確認する事ができます。


Installation

まず、以下のコマンドでapiモードでrailsアプリケーションを作成します。

$ rails new blog --api

Gemfilegraphqlgraphql-rail を加えます。


Gemfile

gem 'graphql'

gem 'graphiql-rails'

通常のrailsアプリならこれだけでセットアップは完了ですが、apiモードとして作成する場合は、config/application.rbに以下の文を加えます。


config/application.rb

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の実行を行なっていきます。

そこで、PostTypeを定義するため、以下のコマンドを流しPost Typeを作成します。

$ rails g graphql:object Post id:ID! title:String! description:String!

以下が上記のコマンドで作成されたファイルの中身です。


app/graphql/types/post_type.rb

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::CreatePostgraphql/types/mutations_type.rbに追記する。


graphql/mutations/create_post.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 に従い、fieldargumentを定義して、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でサーバを立ち上げ GraphQLPostの作成ができるか試してみましょう。

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でサーバを立ち上げ GraphQLPostの更新ができるか試してみましょう。

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にセットします。


graphql/mutations/delete_post.rb

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でサーバを立ち上げ GraphQLPostの削除ができるか試してみましょう。

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


db/seeds.rb

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!

上記のコマンドは以下のファイルを作成します。


app/graphql/types/comment_type.rb

module Types

class CommentType < Types::BaseObject
description 'Comment'

field :id, ID, null: false
field :content, String, null: false
end
end


そして、commentsというfieldPost typeに追加しましょう。


app/graphql/types/post_type.rb

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でサーバを立ち上げ GraphQLPostcommentsが取得できるか試してみましょう。

{

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: falseapp/graphql/types/comment_type.rbに追加しましょう。


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を以下のように変更します。


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でサーバを立ち上げ GraphQLPostcommentsが作成できるか試してみましょう。

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の更新、削除の方法を真似て作成してみてください。


参考