用Rails构建GraphQL服务器的体验🤔💭
不论之前有没有阅读过关于在Rails上创建Web API的方法和优点的文章,都可以进行搭建。
我使用Rails搭建了一个GraphQL服务器,并实现了基本的查询和变更功能。
给定情况下
-
- GraphQLをある程度知っていないとコードの理解は困難です。m(_ _)m
- GraphQLが何なのかは説明しません。ある程度、GraphQLがどういうものかを知っている前提で進めます。
请务必参考官方网站或其他相关信息了解产品规格。
- HTTP リクエストメソッドについては説明しません。
请务必参考MDN(https://developer.mozilla.org/ja/docs/Web/HTTP/Methods)。
- HTTPリクエストボディの説明はしていません。
这本IT用语词典能够让人即使在不明确的情况下也能够有一种”好像懂了”,”虽然不明白但是感觉理解了”的错觉。
这个网站适合初学者,用语解释得很容易理解。
- DBにbooksテーブルがある状態で始めます。
这是一个具有先前字符串的标题和正文栏位的表格。
要将Rails转换为GraphQL服务器,应该怎么做呢?
本次先准备Rails的API模式。(在前一篇文章中已经准备好了。)
还有完整版本的源代码可用。
然后,在Gemfile中添加graphql-ruby,并运行bundle install将其转化为GraphQL服务器。
由于API服务器已经准备就绪,因此可以从安装Gem开始进行进一步操作。
我们将使用Rails来创建GraphQL服务器。
在Gemfile中新增以下内容,并执行安装。
gem 'graphql'
$ bundle install
执行以下命令将生成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决定了返回的数据是什么形式。
$ 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 的文件。
打开文件后,应该会有以下字段定义。
在创建Book模型时,字段将被自动定义。
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
定义一个查询
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开发工具进行验证。
我个人更喜欢用Postman,因此会使用Postman来确认,虽然也可以使用Gem graphiql-rails来在Graphiql等工具中进行确认。
Postman是一款能够帮助开发API的工具。
通过指定URI执行通信请求,并且可以确认返回的数据是什么样的,嗯,可以进行各种确认。
让我们立即尝试通过指定GraphQL查询来获取数据,查看是否成功。
执行Query
-
- rails s でサーバーを起動します。
-
- HTTP verbを POSTにします。
-
- GraphQLのラジをボタンを選択します。
- HTTP リクエストメッセージボディクエリを記述します。

让我们在另一个查询中尝试通过GraphQL请求数据。
获取id为1的书籍的标题。

让我们一次执行多个查询。
我们将获取所有图书的ID以及ID为1的图书的全部信息。

这样一来,你直观地知道查询会返回什么数据了呢!
此外,由于可以只提取所需数据,因此不再会过度获取无用数据。
Mutation的定义
刚才我们使用查询(Query)来获取数据,但是数据的创建、更新和删除将使用“变更”(Mutation)来执行。
我們將開發創建和獲取數據的功能。
本次我们将定义用于创建书籍的变异(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
在字段:book中,定义为Types::BookType,null值为false,但最终会返回以下数据。
{
"data": {
"createBook": {
book: {
"title": "GraphQLな奴",
"body": "GraphQL理解しないとGraphql-rubyわからんね"
}
}
}
}
执行突变

顺便问一句,如果想要以以下的方式接收数据应该怎么做呢?
{
"data": {
"createBook": {
"title": "GraphQLな奴",
"body": "GraphQL理解しないとGraphql-rubyわからんね"
}
}
}
返回的数据形式是通过字段(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的人可能会比较容易理解提及的是Strong Parameters的类型…? ??
- fieldで定義した通りのデータがResolverから(resolveメソッドから)返ってきます。
最后返回数据的方法是resolve。它被称为解析器。
- 今回はbookのtitleとbodyだけを取得するようにしているので、title, bodyの情報が返ってきました。
在执行查询时,只指定了标题(title)和正文(body)字段,所以不会返回其他数据呢。

对于那些觉得找到多种定义很麻烦的人!!!
如果你想在没有指定输入类型的情况下执行查询,只需不继承 GraphQL::Schema::RelayClassicMutation 就可以了。
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变量即可执行查询。

但是,我认为使用input类型会更好,因为它可以像strong parameters一样重复使用,并且在类型方面更方便。
GraphQL有什么优点呢???
我们迄今为止已经使用Query和Mutation来获取和创建数据。
GraphQLのいいところはやはり直感的にどういうデータが返ってくるかが分かりやすい。
データの型も指定できるのでどういう型のデータが来るかも分かりやすいです。
本当に必要な必要最小限のデータだけを柔軟に取得できる
クエリ実行時にbookのidだけ必要であればクエリのfieldにidだけ書けば良いのです。
一つのエンドポイントで、かつ一度のリクエストで色々なデータにアクセス可能
この記事では全データの取得、データの一つを取得、データの作成をたった一つのエンドポイントで成し遂げました。
http://localhost:3000/graphql だけでやりとげました。たくさんRouting定義しなくてもいいわけです。
クエリは一度に複数指定も可能でした。
除此之外,还有许多其他优点,所以我们开始创建这样的API服务器。
从前端怎样执行查询呢?
使用针对Apollo Client 或 GraphQL专用的库会更好。
使用Fetch API或Axios,您可以将查询包含在HTTP POST方法请求的主体中,而无需使用库。
简单来说,只要能进行HTTP请求,就可以执行查询(好像甚至不仅限于HTTP)。
有关Apollo Client的内容超出了文章范围,所以我将不再详细介绍。m(_ _)m
美妙的API,讓GraphQL的生活變得更加精彩٩(ˊωˋ)و✧