相信有很多仁兄在2018年底都看到过 2018 JavaScript 现状调查报告
这篇文章。其中有一张图甚是有趣:
可以看得出来 GraphQL 的趋势大好,而且也越来越受欢迎了,似乎不学习要跟不上时代了。既然这样我们就来学习一下怎么用node搭建GraphQL服务端API架构 (本文将会为您讲述最全面的node GraphQL知识, 文章末尾会附带完整的demo供给各位参考)
GraphQL是什么
简单的说明一下GraphQL是Facebook开源的一种规范应用层查询语言,它具有很多优点:客户端可以自定义查询语句,通过自定义不仅提高了灵活性,而且服务端只返回客户端所需要的数据,减少网络的开销,提高了性能;服务端收到客户端的请求,首先做类型检查,及时反馈,而且更加安全;自动生成文档,降低维护成本;服务端通过新增字段,deprecated字段,避免版本的繁杂和紊乱。
附上官方的文档 GraphQL
附上express实现graphql的文档 express-graphql
GraphQL vs RESTful
RESTful:服务端决定有哪些数据获取方式,客户端只能挑选使用,如果数据过于冗余也只能默默接收再对数据进行处理;而数据不能满足需求则需要请求更多的接口。
GraphQL:给客户端自主选择数据内容的能力,客户端完全自主决定获取信息的内容,服务端负责精确的返回目标数据。
实现GraphQL服务器
首先我们先来实现一个最简单的node GraphQL服务器
项目基础环境
mkdir graphql-api
# 进入项目文件夹
cd graphql-api
# 初始化package文件
npm init # 该命令中的所有步骤全部回车
为了方便下面的步骤,我们在graphql-api文件夹中手动创建下面的目录结构,并安装指定的依赖
# 安装项目依赖
npm install express express-graphql graphql
index.js代码
const express = require('express')
const expressGraphql = require('express-graphql')
const app = express()
// 配置路由
app.use('/graphql', expressGraphql(req => {
return {
schema: require('./graphql/schema'), // graphql相关代码主目录
graphiql: true // 是否开启可视化工具
// ... 此处还有很多参数,为了简化文章,此处就一一举出, 具体可以看 刚才开篇提到的 express文档,
// 也可以在文章末尾拉取项目demo进行查阅
}
}))
// 服务使用3000端口
app.listen(3000, () => {
console.log("graphql server is ok");
});
graphql/schema.js代码
const {
GraphQLSchema,
GraphQLObjectType
} = require('graphql')
// 规范写法,声明query(查询类型接口) 和 mutation(修改类型接口)
module.exports = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
description: '查询数据',
fields: () => ({
// 查询类型接口方法名称
fetchObjectData: require('./queries/fetchObjectData')
})
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
description: '修改数据',
fields: () => ({
// 修改类型接口方法名称
updateData: require('./mutations/updateData')
})
})
})
graphql/queries/fetchObjectData.js代码
先在graphql/queries文件夹下创建fetchObjectData.js文件, 并填入以下代码
const {
GraphQLID,
GraphQLInt,
GraphQLFloat,
GraphQLString,
GraphQLBoolean,
GraphQLNonNull,
GraphQLObjectType
} = require('graphql')
// 定义接口返回的数据结构
const userType = new GraphQLObjectType({
name: 'userItem',
description: '用户信息',
fields: () => ({
id: {
type: GraphQLID,
description: '数据唯一标识'
},
username: {
type: GraphQLString,
description: '用户名'
},
age: {
type: GraphQLInt,
description: '年龄'
},
height: {
type: GraphQLFloat,
description: '身高'
},
isMarried: {
type: GraphQLBoolean,
description: '是否已婚',
deprecationReason: "这个字段现在不需要了"
}
})
})
// 定义接口
module.exports = {
type: userType,
description: 'object类型数据例子',
// 定义接口传参的数据结构
args: {
isReturn: {
type: new GraphQLNonNull(GraphQLBoolean),
description: '是否返回数据'
}
},
resolve: (root, params, context) => {
const { isReturn } = params
if (isReturn) {
// 返回的数据与前面定义的返回数据结构一致
return {
"id": "5bce2b8c7fde05hytsdsc12c",
"username": "Davis",
"age": 23,
"height": 190.5,
"isMarried": true
}
} else {
return null
}
}
}
graphql/mutations/updateData.js代码
先在graphql/mutations文件夹下创建updateData.js文件, 并填入以下代码
const {
GraphQLInt
} = require('graphql')
let count = 0
module.exports = {
type: GraphQLInt, // 定义返回的数据类型
description: '修改例子',
args: { // 定义传参的数据结构
num: {
type: GraphQLInt,
description: '数量'
}
},
resolve: (root, params) => {
let { num } = params
count += num
return count
}
}
好了,到此为止,简单的GraphQL服务器就搭建好了,让我们来启动看看
node index.js // 启动项目
然后我们在浏览器打开 http://localhost:3000/graphql 如下图所示
我们可以看到页面分为3栏,左边的是调用api用的,中间是调用api返回的结果 右边实际上就是我们刚才定义接口相关的东西,也就是api文档。
我们在左边粘贴以下代码:
query fetchObjectData {
fetchObjectData(
isReturn: true
) {
id
username
age
height
isMarried
}
}
mutation updateData {
updateData(
num: 2
)
}
我们点一下左上角的按钮,会发现我们刚才左边填的2个方法名都在这列出来了,我们分别选择 fetchObjectData 和 updateData
api返回结果如下:
自此,我们从创建的GraphQL服务器就算完成了,代码也能正常运行,是否有些小激动!!
别着急,正所谓授人以鱼不如授人以渔,接下来我们细细分析一下我们刚才填写的GraphQL代码,了解其规范语法,这样我们才能真正的掌握GraphQL。
语法规范解析
以下的代码仅为诠释一下GraphQL的规范用法
不要直接拷贝到前面所述的代码中!!
不要直接拷贝到前面所述的代码中!!
不要直接拷贝到前面所述的代码中!!
1、导入GraphQL.js及类型
graphql 无论在定义接口参数和接口返回结果时, 都需要先定义好其中所包含数据结构的类型, 这不难理解,可以理解为我们定义的就是数据模型,其中常用的类型如下。
const {
GraphQLList, // 数组列表
GraphQLObjectType, // 对象
GraphQLString, // 字符串
GraphQLInt, // int类型
GraphQLFloat, // float类型
GraphQLEnumType, // 枚举类型
GraphQLNonNull, // 非空类型
GraphQLSchema // schema(定义接口时使用)
} = require('graphql')
2、定义schema
schema实例中,一般规范为
query: 定义查询类的接口
mutation: 定义修改类的接口
new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query', // 查询实例名称
description: '查询数据', // 接口描述
fields: () => ({
// 查询类型接口方法名称
fetchDataApi1: require('./queries/fetchDataApi1'),
fetchDataApi2: require('./queries/fetchDataApi2'),
fetchDataApi3: require('./queries/fetchDataApi3'),
...
})
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
description: '修改数据',
fields: () => ({
// 修改类型接口方法名称
updateDataApi1: require('./mutations/updateDataApi1'),
updateDataApi2: require('./mutations/updateDataApi2'),
...
})
})
})
3、接口方法定义
// 引用需要用到的数据类型
const {
GraphQLID,
GraphQLString,
GraphQLNonNull,
GraphQLObjectType
} = require('graphql')
// 第一部分 定义接口返回的数据结构
// 不难看出来,下面定义的是
/*
{
id
username
}
*/
const userType = new GraphQLObjectType({
name: 'userItem',
description: '用户信息',
fields: () => ({
id: {
type: GraphQLID,
description: '数据唯一标识'
},
username: {
type: GraphQLString,
description: '用户名'
}
})
})
// 第二部分 定义接口
module.exports = {
type: userType,
description: 'object类型数据例子',
// 定义接口传参的数据结构
args: {
isReturn: {
type: new GraphQLNonNull(GraphQLBoolean),
description: '是否返回数据'
}
},
resolve: (root, params, context) => {
const { isReturn } = params
// 返回的数据与前面定义的返回数据结构一致
return {
"id": "5bce2b8c7fde05hytsdsc12c",
"username": "Davis"
}
}
}
我们来分析一下上图中第一部分的内容,GraphQLObjectType是GraphQL.js定义的对象类型,包括name、description 和fields三个属性:
name: 可作为对象的全局唯一名称
description: 是对象的描述
fields: 是解析函数也就是定义具体数据结构, 该对象包含什么键值
我们再来看一下第二部分的内容
type: 代表这个接口需要返回的数据结构是什么
description: 接口的描述
args: 接口的传参数据结构定义
resolve: 接口内部具体的实现逻辑(需求代码都写在这里面)
我们在处理完业务逻辑之后只需要返回与 type 中定义的数据结构一样的数据即可。
相信这时候你会发现resolve中又有三个参数是什么鬼,稳住,不要慌,这时候我们看回一开始我们的index.js文件
省略号这个地方是可以再定义很多参数的,而 rootValue和context这两个参数正好就对应了接口resolve方法中的root和resolve,我们可以看看官方文档对这两个参数的解释:
一般我们可以通过rootValue来做api认证,context可以传递用户信息、数据库链接等,也可以不用,这完全取决于你的业务场景
总结
1、简单点说,其实我们在编写GraphQL 代码的时候,你可以理解为他跟typescript有些相似,都需要事先定义好传参的数据结构,返回结果的数据结构,虽然这个比方不合理,但是这样讲更通俗易懂些
2、description 虽然不是必填的,但是建议都写上,因为它的作用其实就是api文档的解释。
3、GraphQL 真的很强大,虽然一开始接触的时候会有点蒙,啥玩意???这定义那定义的,虽然比RESTful风格的api上手难理解很多,但是我相信,当你学会使用GraphQL的时候,你会发现这玩意还是挺有意思的。
4、GraphQL 的用法还有很多,为了文章尽量简单易懂, 本文只是列出了其中一种用法,在我的demo中还有更多的用法,比如GraphQLEnumType(枚举类型)、GraphQLUnionType(联合类型)、GraphQLInterfaceType(接口类型)...有兴趣的看一下。
附上本人完整的demo: graphql-api