一年多之前就据说了
GraphQL
,前段时间接触了一个海归团队(创始人就来自Facebook),技术栈使用Graphql+Apollo+React
,在他们的指导下试用了一下以为真心酷。遂花了半个多月了解了GraphQL
的主要思想和基本用法,碰巧看到一篇文章)把GraphQL的核心概念讲的比较清晰易懂,依据该文,大体翻译以下,若是你也对GraphQL
感兴趣,欢迎一块儿来讨论。node
什么是GraphQL:git
给API设计的一种查询语言,一个依据已有数据执行查询的运行时,为你的API中的数据提供一种彻底且容易理解的描述,使得API可以更容易的随着时间而演变,还支持强大的开发者工具。github
虽然名字叫作GraphQL 可是和数据库自己并无直接关系。数据库
GraphQL的特征:express
GraphQL的核心包括Query
,Mutation
,Schemas
等等,每一个概念下又有一些子概念,下面分别作简单的介绍:npm
Queries用作读操做,也就是从服务器获取数据。Queries定义了咱们对模式执行的行为。下面是一个简单的查询及相应的结果:json
// Basic GraphQL Query { author { name posts { title } } }
// Basic GraphQL Query Response { "data": { "author": { "name": "Chimezie Enyinnaya", "posts": [ { "title": "How to build a collaborative note app using Laravel" }, { "title": "Event-Driven Laravel Applications" } ] } } }
查询和响应具有相同的结构。浏览器
若是一个操做没有type
,GraphQL默认会把这些操做看作query
。query
还能够拥有名字,虽然是可选的,可是能够帮助识别某个query是作什么的。bash
query也能够拥有注释,注释以#
开头。服务器
Field是咱们想从服务器获取的对象的基本组成部分。上述代码中name
就是author
对象的一个Field
.
和普通的函数同样,query
能够拥有参数,参数是可选的或需求的。参数使用方法以下:
{ author(id: 5) { name } }
须要注意的是,GraphQL中的字符串须要包装在双引号中。
除了参数,query还容许你使用变量来让参数可动态变化,变量以$
开头书写,使用方式以下:
query GetAuthor($authorID: Int!) { author(id: $authorID) { name } }
参数能够拥有默认值:
query GetAuthor($authorID: Int! = 5) { author(id: $authorID) { name } }
参数也能够是可选的或必须的,好比上述的$authorID
变量是必须的,它的定义中包含了!
。详细信息可见schema
中。
别名,好比说,咱们想分别获取ID5和7,咱们能够用下面的方法:
{ author(id: 5) { name } author(id: 7) { name } }
因为存在相同的name
,上述代码会报错,要解决这个问题就要用到别名了Allases
。
{ chimezie: author(id: 5) { name } neo: author(id: 7) { name } }
获取的结果以下:
# Response { "data": { "chimezie": { "name": "Chimezie Enyinnaya" }, "neo": { "name": "Neo Ighodaro" } } }
Fragments
是一套在queries
中可复用的fields
。好比说咱们想获取twitterHandle
field,咱们能够按下面这样作:
{ chimezie: author(id: 5) { name twitterHandle } neo: author(id: 7) { name twitterHandle } }
可是若是fields
过多,就会显得重复和冗余。Fragments在此时就能够起做用了。如下是使用Fragments
的语法:
{ chimezie: author(id: 5) { ...authorDetails } neo: author(id: 7) { ...authorDetails } } fragment authorDetails on Author { name twitterHandle }
Directives
提供了一种动态使用变量改变咱们的queries
的方法。如本例,咱们会用到如下两个directive
:
@include
:只有当if
中的参数为true
时,才会包含对应fragment
或field
;@skip
:当if
中的参数为true
时,会跳过对应fragment
或field
;这两个directive
都接受一个布尔值做为参数;
实例以下:
query GetAuthor($authorID: Int!, $notOnTwitter: Boolean!, $hasPosts: Post) { author(id: $authorID) { name twitterHandle @skip(if: $notOnTwitter) posts @include(if: $hasPosts) { title } } }
传统的API使用场景中,咱们会有须要修改服务器上数据的场景,mutations
就是应这种场景而生。mutations
被用以执行写操做,经过mutations
咱们会给服务器发送请求来修改和更新数据,而且会接收到包含更新数据的反馈。mutations
和queries
具备相似的语法,仅有些许的差异。
mutation UpdateAuthorDetails($authorID: Int!, $twitterHandle: String!) { updateAuthor(id: $authorID, twitterHandle: $twitterHandle) { twitterHandle } }
咱们在mutation
中以数据为载物发送,好比上面的例子中,咱们发送了下面的数据:
# Update data { "authorID": 5, "twitterHandle": "ammezie" }
更新完成后,咱们将从服务器获取如下内容做为反馈。
# Response after update { "data": { "id": 5, "twitterHandle": "ammezie" } }
咱们能够看出,反馈数据中包含咱们更新的数据
和queries
相似,mutations
也可以接受,多重fields
,不过queries
和mutations
的一个重大不一样之处在于,为了保证数据的完整性mutations
是串形执行,而queries
能够并行执行。
Schemas 描述了 数据的组织形态 以及服务器上的那些数据可以被查询,Schemas提供了你数据中可用的数据的对象类型,GraphQL中的对象是强类型的,所以schema中定义的全部的对象必须具有类型。类型容许GraphQL服务器肯定查询是否有效或者是否在运行时。Schemas可用是两种类型Query
和Mutation
。
Schemas
用GraphQL schemas语言构建,它和咱们前面已经学过的query很是相似,下面是一个示例:
type Author { name: String! posts: [Post] }
上面的schemas
定义了一个Author
对象,它包含两个fields
(name
和posts
),这意味着当咱们操做(读取)Author
时,咱们只能使用name
和fields
,每一个field
均可以是必须的或者可选的,好比上面的name
field是必须的,由于其后有符号!
,而posts
是可选的。
Schemas中的Fields 也能够接收参数,这些参数能够是可选的或者必须的,必须的参数经过!
识别。
type Post { allowComments(comments: Boolean!) }
顺便提一下,GraphQL
支持如下标量类型:
由上述类型定义的fields 不能 再拥有本身的 fields,咱们可使用scalar
关键字,自定义标量类型,好比咱们能够定义一个Date
类型:
scalar Date
又称Enums
,这是一种特殊的标量类型,经过此类型,咱们能够限制值为一组特殊的值,好比,咱们能够:
Enums
经过关键字enum
进行定义:
enum Media { Image Video }
input类型对mutations来讲很是重要,在 GraphQL schema 语言中,它看起来和常规的对象类型很是相似,可是咱们使用关键字input
而非type
,input
类型按以下定义:
# Input Type input CommentInput { body: String! }
咱们将使用node.js
建设一个简单的任务管理GraphQL serve
,这个例子很是简单,可是足以用到咱们学过的大部分概念,巩固咱们的学习成果。
Node.js
服务器咱们使用Express
作为咱们的node.js
框架,首先咱们须要初始化一个node.js
项目,使用如下命令:
mkdir graphql-tasks-server cd graphql-tasks-server npm init -y npm install express body-parser apollo-server-express graphql graphql-tools lodash --save
/src/
/src/data/
/src/data/data.js
:/src/schema/
/src/schema/index.js
/src/schema/resolvers.js
/src/server.js
// src/server.js const express = require('express'); const bodyParser = require('body-parser'); const { graphqlExpress, graphiqlExpress } = require('apollo-server-express'); const schema = require('./schema/index'); const PORT = 3000; const app = express(); // Graphql 用以构建Graph服务器 app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })); // Graphiql 用以展现查询客户端 app.use('/graphiql', graphiqlExpress({ endpointURL: 'graphql' })); app.listen(PORT, () => console.log(`GraphiQL is running on http://localhost:${PORT}/graphiql`));
// src/schema/index.js const { makeExecutableSchema } = require('graphql-tools'); const resolvers = require('./resolvers'); const typeDefs = ` type Project { id: Int! name: String! tasks: [Task] } type Task { id: Int! title: String! project: Project completed: Boolean! } type Query { projectByName(name: String!): Project fetchTasks: [Task] getTask(id: Int!): Task } type Mutation { markAsCompleted(taskID: Int!): Task } `; module.exports = makeExecutableSchema({ typeDefs, resolvers });
咱们为咱们的app定义了schema
,咱们定义了两种类型,projects
和tasks
,type task中包含咱们要完成的任务,而type Project中包含三项id
,name
和tasks
,id
和name
是必备fields
,tasks
则是一系列Task
类型的组合,task
type包含4项,id
, title
, project
和 completed
,id
和title
是必须的,project
指明了属于那个项目,而completed
代表了其完成状况。
一个项目能够包含多个任务,而一个任务必属于一个项目。
接下来,咱们定义了一系列查询,
projectByName
:用以经过传入的name
参数来返回对应的Project
;fetchTasks
:用以获取全部的任务并返回;getTasks
:依据传入的id
返回特定的任务;咱们也定义了一些Mutation
:
markAsCompleted
:接受一个id作为参数,并返回修改完成状态后的Taskresolver
是决定schemas
中的field
该如何执行的函数。
Tip: GraphQL resolvers 能够返回Promises
// src/schema/resolvers.js const _ = require('lodash'); // Sample data const { projects, tasks } = require('./../data/data'); const resolvers = { Query: { // Get a project by name projectByName: (root, { name }) => _.find(projects, { name: name }), // Fetch all tasks fetchTasks: () => tasks, // Get a task by ID getTask: (root, { id }) => _.find(tasks, { id: id }), }, Mutation: { // Mark a task as completed markAsCompleted: (root, { taskID }) => { const task = _.find(tasks, { id: taskID }); // Throw error if the task doesn't exist if (!task) { throw new Error(`Couldn't find the task with id ${taskID}`); } // Throw error if task is already completed if (task.completed === true) { throw new Error(`Task with id ${taskID} is already completed`); } task.completed = true; return task; } }, Project: { tasks: (project) => _.filter(tasks, { projectID: project.id }) }, Task: { project: (task) => _.find(projects, { id: task.projectID }) } }; module.exports = resolvers;
咱们对Schemas中定义的各项添加了处理函数。
// src/data/data.js const projects = [ { id: 1, name: 'Learn React Native' }, { id: 2, name: 'Workout' }, ]; const tasks = [ { id: 1, title: 'Install Node', completed: true, projectID: 1 }, { id: 2, title: 'Install React Native CLI:', completed: false, projectID: 1 }, { id: 3, title: 'Install Xcode', completed: false, projectID: 1 }, { id: 4, title: 'Morning Jog', completed: true, projectID: 2 }, { id: 5, title: 'Visit the gym', completed: false, projectID: 2 } ]; module.exports = { projects, tasks };
node src/server.js
在浏览器中打开,http://localhost:3000/graphiql,输入查询便可看到结果。