此篇文章介绍 graphql 基础知识,会过于无聊。若是你想快速上手,可使用个人脚手架 shfshanyue/apollo-server-starter。若是你想用它写一个前端,能够参考个人 shfshanyue/shici。javascript
本文地址: graphql schema and query前端
咱们先写一个关于 graphql.js
的 hello, world
示例,而且围绕它展开对 graphql
的学习java
const {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
} = require('graphql')
// schema,由 web 框架实现时,这部分放在后端里
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve() {
return 'hello, world'
}
}
}
})
})
// query,由 web 框架实现时,这部分放在前端里
const query = '{ hello }'
// 查询,这部分放在服务端里
graphql(schema, query).then(result => {
// {
// data: { hello: "hello, world" }
// }
console.log(result)
})
复制代码
由上,也能够看出 graphql
很关键的两个要素:schema
和 query
。而当咱们开发 web 应用时,schema
将会是服务端的主体,而 query
存在于前端中,相似 REST 中的 API。react
咱们先抽出 schema
的代码部分,这里有 GraphQLSchema
,GraphQLObjectType
和 GraphQLString
等诸多 API。git
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve() {
return 'hello, world'
}
}
}
})
})
复制代码
正如在 React
中使用jsx
简化了 React.createElement
的写法。graphql
对于 schema 也有一套它本身的 DSL (Domain Specified Language)
,也更为简单,易懂。在代码中以 graphql
或 gql
做为文件名后缀,用法以下github
# schama,在后端进行维护
type Query {
hello: String
}
# query,在前端进行管理
{
hello
}
复制代码
以及查询结果web
{
hello: 'hello, world'
}
复制代码
在前端中全部查询的 gql 每每会经过 graphql/gql 后缀的文件来统一维护,这里有一份代码示例: shfshanyue/shici:query.gql面试
你看到这里,想必有两个疑问:sql
graphql
表明什么,以及咱们如何书写 graphql
DSL
少了一个 resolve
函数,而它又是什么这里引入 graphql 中的两个基本术语,object type
与 field
。它们是组成 graphql 最基本的组件,如同细胞是生物体的基本单位。docker
这里来一个更复杂的 schema
,以下所示
# schema
schema {
query: Query
}
type Query {
hello: String
todos: [Todo!]!
}
type Todo {
id: ID!
title: String!
}
# query
{
# 只能查询 Query 下的字段
todos {
id
title
}
hello
}
复制代码
若是说 graphql 是数据库的进一步抽象,则 object type
相似于 sql 中的 table
,field
相似于 sql 中的 column
。
那咱们仔细审视以上示例,能从其中获得一些信息:
type
标注为 graphql.js
中的类型: GraphQLObjectType
{}
表明一个 query
(查询),其中由若干字段组成,用以查你所须要的数据Query
是一个特殊的 object type
,表示为 RootQueryType
,它会放到 schema
中。如同C语言中的 main
函数,能够理解为 graphql
的入口查询。正因如此,它所包含的 field
没有紧密的内关联关系。hello
与 todos
是 Query 下的两个 field
,一切前端的查询均要从 Query
的 field
查起。如在以上的 query 示例中,只能查询 todos
与 hello
。Todo
是一个自定义名称的 object type
,能够理解为对应数据库中的一个 todo 的表。id
与 title
是 Todo 下的 field
,能够理解他们为 Todo 的属性,它们每每由一些基本属性以及聚合属性 (count, sum) 组成。[Todo!]!
表明返回结果将是一个 Todo
的数组。[]
表明返回为数组,!
表明返回不能为空,[!]
表明数组中的每一项都不能为空。id: ID!
表明 Todo 的 id 全局惟一title: String!
表明 Todo 的 title 是不为空的字符串到了这里,你会发现,对于 graphql schema 的认识还有一些信息还没有涉及:ID
与 String
,它们被称做 scalar type
,你能够理解为数据类型。 正是由于 scalar,graphql 才成为强类型查询语言。
由上所述,Query
为 graphql
的入口查询处,咱们能够而且只能够查询 Query
下的任意字段 (field)。所以,他组成了 graphql
最核心的功能: 查找你所须要的任何数据。
# schema
type Query {
hello: String
todos: [Todo!]!
}
// 如下三个 query 通常会在前端进行统一管理
# query 1
{
hello
}
# query 2
{
todos {
id
}
}
# query 3
{
hello
todos {
id
title
}
}
复制代码
查询结果以下
{ hello: 'hello, world' }
{ todos: [{ id: 1 }] }
{ hello: 'hello, world', todos: [{ id: 1, title: 'learn react' }] }
复制代码
在前端咱们根据 Query
组合成各类查询,而咱们为了在查询过程当中方便辨认,能够为查询添加 operationName
:
query HELLO {
hello
}
query TODOS {
todos {
id
}
}
query HELLO_AND_TODOS {
hello
todos {
id
title
}
}
复制代码
在 graphql
中有一些内置的 scalar
类型,用以表示 graphql 中 field
的数据类型,这也是 graphql 为强类型语言的基础。内置类型以下所示
Int
,表明32位有符号型整数Float
String
Boolean
ID
,惟一标识符,通常可视为数据库中的主键。在 object type
中,通常会把 id 设置为 ID
类型,依赖它作一些缓存的操做。正由于 scalar
与 !
,来保证了 graphql 的 query 是强类型的。因此当咱们看到以下的 query 时,能够在前端大胆放心的使用: data.todos.map(todo => todo.title.replace(' ', ''))
。既不用担忧 data.todos
忽然报错 Cannot read property 'map' of null
,也不用担忧 Cannot read property 'title' of null
# schema
type Query {
hello: String
todos: [Todo!]!
}
type Todo {
id: ID!
title: String!
}
# query
{
todos {
id
title
}
}
复制代码
若是不使用 graphql,你没法保证响应中数据的类型。你可能须要使用 lodash 来作一些边界的处理
const data = {
todos: [{
id: 1,
title: 'learn react'
}]
}
// 使用 graphql 后
data.todos.map(todo => todo.title.replace(' ', ''))
// 若是不使用 graphql,你没法保证返回数据类型。你可能须要使用 lodash 来作一些边界的处理
_.map(data.todos, todo => _.replace(todo.title, ' ', ''))
复制代码
当在数据库中,一个字段除了整型,浮点型等基本类型外,还会有更多并且比较重要和经常使用的数据类型:json
和 datetime
。既然 scalar
用以表示 field
的数据类型,那么它如何表示 json
与 datetime
或者更多的数据类型呢?
这时,可使用 graphql.js
的API graphql.GraphqlQLScalarType
来自定义 scalar
再回到刚开始的 hello, world
的示例,用 graphql
表示以下
# schema
type Query {
hello: String
}
# query
query HELLO {
hello
}
复制代码
对以上章节的内容再梳理一遍:
hello
进行查询,由于该字段在 Query
下HELLO
查询所获得的 data.hello
是一个字符串恩?咱们好像把最重要的内容给漏了,hello
中的内容究竟是什么?!而 resolve
函数就是作这个事的
// 使用 graphql.js 的写法,把 schema 与 resolve 写在一块儿
new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve() {
return 'hello, world'
}
}
}
})
// 单独把 resolve 函数给写出来
function Query_hello_resolve () {
return 'hello, world'
}
复制代码
因而咱们再补齐以上内容
# schema
type Query {
hello: String
}
# query
{
hello
}
复制代码
由此获得的数据示例
{
hello: 'hello, world'
}
复制代码
查看一个很典型的 REST 服务端的一段逻辑:抽取用户ID以及读取参数(querystring/body)
app.use('/', (ctx, next) {
ctx.user.id = getUserIdByToken(ctx.headers.authorization)
next()
})
app.get('/todos', (ctx) => {
const userId = ctx.user.id
const status = args.status
return db.Todo.findAll({})
})
复制代码
而在 graphql 中,使用 resolve
函数为 field
提供数据,而 context,args 都会做为 resolve
函数的参数
# schema
type Query {
# 如同 REST 通常,能够携带参数,并显式声明
todos (status: String): [Todo!]!
}
type Todo {
id: ID!
title: String!
}
# query
{
# 查询时,在这里指定参数 (args)
todos (status: "TODO") {
id
title
}
# 同时也能够指定别名,特别是当有 args 时
done: todos (status: "DONE") {
id
title
}
}
# query with variables
query TODOS ($status: String) {
done: todos (status: $status) {
id
title
}
}
复制代码
返回数据示例
{
todos: [{ id: 1, title: '松风吹解带' }],
done: [{ id: 2, title: '山月照弹琴' }],
}
复制代码
固然,咱们也是经过 Query
以及 Todo 的 resolve 函数来肯定内容,对于如何获取以上数据以下所示
// Query 的 resolve 函数
const Query = {
todos (obj, args, ctx, info) {
// 从 ctx 中取一些上下文信息,如最多见的 user
const userId = ctx.user.id
const status = args.status
return db.Todo.findAll({})
}
}
// Todo 的 resolve 函数
const Todo = {
title (obj) {
return obj.title
}
}
复制代码
object type
,如 Todo.title
中 obj
表示 todo
GraphQLResolveInfo
,关于本次查询的元信息,好比 AST,你能够对它进行解析从这里能够看出来:graphql 的参数都是显式声明,而且强类型。这一点比 REST 要好一些
# query with variables
query TODOS ($status: String) {
done: todos (status: $status) {
id
title
}
}
复制代码
graphql
可以简化一切的查询,或者说它是简化了服务端开发人员 CRUD
中的 Read
。那么,如何对资源进行修改呢?这里就提到了 Mutation
。
# 在后端的 schema
schema {
query: Query
mutation: Mutation
}
type Mutation {
addTodo (title: String!): Todo
}
# 在前端的 query
mutation ADD_TODO {
addTodo (title: "学习 React") {
id
title
}
}
复制代码
// 以上示例返回结果
{
addTodo: { id: 128, title: '学习React' }
}
复制代码
以上是一个添加 Todo 的例子,从这里能够注意到几点
Mutation
与 Query
一样属于特殊的 object type
,一样,全部关于数据的更改操做都要从 Mutation
中找起,也须要放到 schema
中Mutation
与 Query
分别为 graphql 的两大类操做,在前端进行 Mutation
查询时,须要添加 mutation
字段 (Query
查询时,在前端添加 query
字段,但这不是必选的)type Mutation {
addTodo (title: String!): Todo
}
mutation ADD_TODO {
addTodo (title: "学习 React") {
id
title
}
}
复制代码
以上是一个关于添加 Todo 的 mutation,在咱们添加一个 Todo 时,它仅有一个属性: title。若是它拥有更多个属性呢?这时,可使用 input type
,把某一资源的全部属性聚合起来。而且配合 variables
一块儿使用传递数据
input InputTodo {
title: String!
}
type Mutation {
addTodo (todo: InputTodo!): Todo
}
mutation ADD_TODO ($todo: InputTodo!) {
addTodo (todo: $todo) {
id
title
}
}
复制代码
// $todo 的值,在前端获取数据时,使用 variables 传入
{
title: '学习 React'
}
复制代码
我是山月,我会按期分享全栈文章在我的公众号中。若是你对全栈面试,前端工程化,graphql,devops,我的服务器运维以及微服务感兴趣的话,能够关注我。若是想进群交流,能够添加我微信 shanyue94,备注加群。