GraphQL是API的查询语言,不依赖任何前端后台技术。在服务器端有很多种实现。
本例使用的是graphql-ruby(rails) ,用来解析传入的查询,并进行数据库调用,响应返回JSON,替换rails的api。
上面的5个部分配合工作,完成各种API的功能。
创建一个rails api的项目来体验下graphql。
rails new graphql_api --api
rails g model User email:string name:string
rails g model Book title:stirng summary:string user:belongs_to
rails db:migrate
在user.rb里面加入 has_many :books
可以通过faker创建一些数据,比如:
5.times do
user = User.create(name: Faker::Name.name, email: Faker::Internet.email)
5.times do
user.books.create(title: Faker::Book.title)
end
end
rake db:seed
生成GraphQL文件
rails generate graphql:install
bundle
rails generate graphql:object user
rails generate graphql:object book
创建graphql目录和2个新的自定义类型User和Book
├─ controllers
+ │ └─ graphql_controller.rb
+ ├─ graphql
+ │ ├─ mutations
+ │ ├─ rails_graphql_demo_schema.rb
+ │ └─ types
+ │ ─ base_enum.rb
+ │ ─ base_input_object.rb
+ │ ─ base_interface.rb
+ │ ─ base_object.rb
+ │ ─ base_scalar.rb
+ │ ─ base_union.rb
+ │ ─ book_type.rb
+ │ ─ mutation_type.rb
+ │ ─ query_type.rb
+ │ ─ user_type.rb
同时也会创建app/controllers/graphql_controller.rb#execute,作为api访问的入口,开发环境增加一个graphiql-rails的gem包
# routes.rb
Rails.application.routes.draw do
if Rails.env.development?
mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "graphql#execute"
end
post "/graphql", to: "graphql#execute"
end
现在启动项目,访问http://localhost:3000/graphiql,就可以看到测试api接口的页面
我们还要创意一系列类型Type,便于GraphQL知道,GraphQL的类型(Tyep)等同于API的Model,也要指定字段(Fields)、方法(functions)等用于返回到客户端的应用程序。
每个字段都有个‘类型’以及是否允许为null,告诉graphql对传入和传出的数据的要求,便于传给前端和后端正确的数据。
:id, :name这些字段会对应之前创建的User模型中的字段。
这里定义了一个自定义的字段:books_count, Rails模型中并不存在,因此我们将其定义在字段列表的下方,
自定义的方法在models's scope,可以通过self.books.size调用。
# app/graphql/types/user_type.rb
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: true
field :email, String, null: true
field :books, [Types::BookType], null: true
field :books_count, Integer, null: true
def books_count
books.size
end
end
end
# app/graphql/types/book_type.rb
module Types
class BookType < Types::BaseObject
field :title, String, null: false
end
end
query_type.rb和mutation_type.rb这两种传入请求的路由,定义在模式schema里面,他们和Rails路由和资源有些相似。
# app/graphql/RAILS_APP_NAME_schema.rb
class GraphqlApiSchema < GraphQL::Schema
mutation(Types::MutationType)
query(Types::QueryType)
end
在query_type文件里面,定义:users和:user的字段,以及他们的方法,
users方法返回UserType类型的一组对象。
user方法接收一个类型为ID的:id的参数,返回一个UserType对象,(ID是一个内置的类型)
# app/graphql/types/query_type.rb
module Types
class QueryType < Types::BaseObject
field :users, [Types::UserType], null: false
def users
User.all
end
field :user, Types::UserType, null: false do
argument :id, ID, required: true
end
def user(id:)
User.find(id)
end
end
end
访问http://localhost:3000/graphiql,
在您的浏览器上粘贴users
的查询代码,
我们在上面添加了查询字段。在这里,我们确切地指定了我们希望API响应的内容; 在这种情况下,我们只需要一个用户名,电子邮件列表和他们拥有的书籍数量。
query {
users {
name
email
booksCount
}
}
还可以查询单个用户相关信息:
query {
user(id: 1) {
name
email
books {
title
}
}
}
Mutations允许创建、修改、销毁数据,我们设置一个基类,用来扩展CreateUser的mutation。
# app/graphql/mutations/base_mutation.rb
class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation
end
Arguments参数:在这里我们指定接受哪些参数作为参数,以及它们是什么对象类型, 这是必需的。这有点类似于在Rails控制器中定义强参数,这里对进入的内容进行更细粒度的控制。
Fields字段:和上面查询字段概念相同,接收参数创建对象,同时希望返回一个user带有我们的新模型的字段,并附带一个errors数组。
Resolver解析器:resolve方法是执行ActiveRecord命令的地方。他返回一个带有和上面定义的字段名称一样的键的hash.
# app/graphql/mutations/create_user.rb
class Mutations::CreateUser < Mutations::BaseMutation
argument :name, String, required: true
argument :email, String, required: true
field :user, Types::UserType, null: false
field :errors, [String], null: false
def resolve(name:, email:)
user = User.new(name: name, email: email)
if user.save
# Successful creation, return the created object with no errors
{
user: user,
errors: [],
}
else
# Failed save, return the errors to the client
{
user: nil,
errors: user.errors.full_messages
}
end
end
end
最后,将新突变mutation添加到主突变类型类中,以便它暴露给我们的API。
# app/graphql/types/mutation_type.rb
module Types
class MutationType < Types::BaseObject
field :create_user, mutation: Mutations::CreateUser
end
end
测试,请打开http://localhost:3000/graphiql
并粘贴以下查询。
注意我们传入一个createUser(input: {})
对象; 这映射到:create_user
接受单个input
参数的字段。在graphql-ruby的文档中了解有关此设计的更多信息。
mutation {
createUser(input: {
name: "Matt Boldt",
email: "me@mattboldt.com"
}) {
user {
id
name
email
}
errors
}
}
成功!我们刚刚通过GraphQL创建了第一个模型; 不需要额外的路由,控制器或序列化器。更重要的是,我们只从新创建的模型中准确返回了所需的数据。