看完复联四,我整理了这份 GraphQL 入门教程,哈哈真香。。。javascript
首先有请阿爸镇贴!哈哈哈,须要高清原图的小伙伴能够 点我下载 阿爸无敌 。前端
下面开始本文内容:java
GraphQL
是 Facebook 开发的一种 API 的查询语言,与 2015 年公开发布,是 REST API 的替代品。node
GraphQL
既是一种用于 API 的查询语言也是一个知足你数据查询的运行时。 GraphQL
对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端可以准确地得到它须要的数据,并且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。git
官网: graphql.org/
中文网: graphql.cn/github
如:hero
中有 name
, age
, sex
等,能够只取得须要的字段。mongodb
典型的 REST API
请求多个资源时得载入多个 URL,而 GraphQL
能够经过一次请求就获取你应用所需的全部数据。这样也能保证在较慢的移动网络链接下,使用 GraphQL
的应用也能表现得足够迅速。chrome
GraphQL
使用类型来保证应用只请求可能的数据,还提供了清晰的辅助性错误信息。应用可使用类型,而避免编写手动解析代码。数据库
这里先看下简单案例,体验下 GraphQL
的神奇之处(后面详细介绍)。
咱们这样定义查询语句:
query {
hero
}
复制代码
而后获得的就是咱们所要查询的 hero
字段:
{
"data": {
"hero": "I'm iron man"
}
}
复制代码
这样用起来,是否是更舒服呢?
全称:Representational State Transfer
表属性状态转移。
本质上就是定义 uri ,经过 API 接口来取得资源。通用系统架构,不受语言限制。
例子: 饿了吗接口。
如:接口 restapi/shopping/v3/restaurants?latitude=13
就是个典型的 restful
接口,定义资源 + 查询条件。
restful
一个接口只能返回一个资源,GraphQL
一次能够获取多个资源。
restful
用不一样 url 来区分资源,GraphQL
用类型区分资源。
首先建立一个文件夹 demo
,并初始化一个 package.json
,安装 express
/ graphql
/ express-graphql
依赖包:
npm init -y
npm install express graphql express-graphql -S
复制代码
新建一个 hello.js
,引入文件:
const express = require('express')
const { buildSchema } = require('graphql')
const graphqlHTTP = require('express-graphql')
复制代码
建立一个 schema
来定义查询语句和类型,buildSchema()
方法须要传入的参数是字符串类型,以下面的 hero
查询字段,后面的 String
类型表示字段返回的数据类型:
const schema = buildSchema(` type Query { hero: String } `)
复制代码
建立一个 root
处理器,处理对应的查询,这里的 hello
处理器对应的是 schema
中的 hero
字段查询的处理,这里直接返回 I'm iron man
的结果:
const root = {
hero: () => {
return "I'm iron man"
}
}
复制代码
固然,处理器中也能够是其余复杂操做,后面会介绍。
而后实例化 express
,而且将路由转发给 graphqlHTTP
处理:
const app = express()
app.use('/graphql', graphqlHTTP({
schema,
rootValue: root,
graphiql: true
}))
app.listen(3000)
复制代码
graphqlHTTP
中的三个参数介绍:
schema
:定义的查询语句和类型rootValue
:处理对应查询的处理器graphiql
:是否开启调试窗口,开发阶段开启,生产阶段关闭接下来运行项目,在命令行中执行 node hello.js
,这里能够在 graphiql
上作调试,打开地址 localhost:3000/graphiql
就能够愉快的查询了。
另外咱们能够在 graphiql
界面右侧打开 Docs 查看咱们定义的全部字段和描述信息。
最终代码:
const express = require('express')
const { buildSchema } = require('graphql')
const graphqlHTTP = require('express-graphql')
// 构建schema,这里定义查询的语句和类型
const schema = buildSchema(` type Query { hero: String } `)
// 定义查询所对应的 resolver,也就是查询对应的处理器
const root = {
hero: () => {
return "I'm iron man"
}
}
const app = express()
// 将路由转发给 graphqlHTTP 处理
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true
}))
app.listen(3000)
复制代码
咱们前面的查询中,已经将 hero
字段定义为 String
类型,可是经常开发中,咱们又会碰到字段是多个类型,即字段也能指代对象类型(Object),好比一个 user
字段会有 name
、age
等字段,而 name
返回字符串类型,age
返回数值类型。
这时候,咱们能够对这个对象的字段进行次级选择(sub-selection)。GraphQL 查询可以遍历相关对象及其字段,使得客户端能够一次请求查询大量相关数据,而不像传统 REST 架构中那样须要屡次往返查询。
咱们能够新建一个查询类型来定义 user
字段返回的类型:
const schema = buildSchema(` type User { # 查询能够有备注! name: String age: Int } type Query { hero: String user: User } `)
复制代码
在处理器中咱们也要加上:
const root = {
hero: () => {
return "I'm iron man"
},
user: () => {
return {
name: 'leo',
age: 18
}
}
}
复制代码
这边 Int/String 参数类型的问题,下一章介绍
String
, Int
, Float
, Boolean
和 ID
,这些基本参数类型能够在 schema
声明中直接使用。
Int
:有符号 32 位整数。Float
:有符号双精度浮点值。String
:UTF‐8
字符序列。Boolean
:true
或者 false
。ID
:ID
标量类型表示一个惟一标识符,一般用以从新获取对象或者做为缓存中的键。ID
类型使用和 String
同样的方式序列化;然而将其定义为 ID
意味着并不须要人类可读型。另外,咱们可使用 [类型]
来表示一类数组,如:
[Int]
表示整型数组;[String]
表示字符串型数组;使用方式和 JS 参数传递同样,小括号内定义形参,可是参数须要定义类型。
使用 !
表明参数不能为空。
下面案例:参数 teamName
是 String
类型,必须传递,而 number
参数也是 Int
类型,可是是非必须传递,最后输出的结果也是 String
类型。
type Query {
getHero(teamName: String!, number: Int): [String]
}
复制代码
下面一个案例:
//...省略其余
const schema = buildSchema(` type Query { getHero(teamName: String!): [String] } `)
const root = {
getHero: ({teamName}) => {
// 这里的操做 实际开发中经常用在请求数据库
const hero = {
'三国': ['张飞', '刘备', '关羽'],
'复仇者联盟': ['钢铁侠', '美国队长', '绿巨人']
}
return hero[teamName]
}
}
//...省略其余
复制代码
这时候咱们在 GraphiQL 上输入查询,就会获得 复仇者联盟 的英雄数据了。
// 查询
query {
getHero(teamName:"复仇者联盟")
}
// 结果
{
"data": {
"getHero": [
"钢铁侠",
"美国队长",
"绿巨人"
]
}
}
复制代码
在实际开发中,咱们返回的数据类型多是一个对象,对象中可能既有 Int
类型的属性,也有 String
类型的值,等等,这里咱们可使用 自定义返回类型 来处理:
//...省略其余
const schema = buildSchema(` type Hero { name: String age: Int doSomething(thing: String): String } type Query { getSuperHero(heroName: String!): Hero } `)
const root = {
getSuperHero: ({heroName}) => {
// 这里的操做 实际开发中经常用在请求数据库
const name = heroName
const age = 18
const doSomething = ({thing}) => {
return `I'm ${name}, I'm ${thing} now`
}
return { name, age, doSomething }
}
}
//...省略其余
复制代码
这里指定了 getSuperHero
字段的返回类型是 Hero
类型,随后在上面定义了 Hero
。
其中 Hero
类型中的 doSomething
也是能够传递指定类型参数,而且指定返回类型。
下面看下输出状况:
// 查询
query {
getSuperHero(heroName:"IronMan") {
name
age
doSomething
}
}
// 结果
{
"data": {
"getSuperHero": {
"name": "IronMan",
"age": 46,
"doSomething": "I'm IronMan, I'm undefined now"
}
}
}
复制代码
这里也能够给 doSomething
传递参数,就会获取到不一样结果:
// 查询
query {
getSuperHero(heroName:"IronMan") {
name
age
doSomething(thing:"watching TV")
}
}
// 结果
{
"data": {
"getSuperHero": {
"name": "IronMan",
"age": 46,
"doSomething": "I'm IronMan, I'm watching TV now"
}
}
}
复制代码
这一节咱们学习如何在客户端中访问 graphql
的接口。
咱们先在后端将接口开发完成,这里跟前面差很少,但须要多一步,使用 express
向外暴露一个文件夹,供用户访问静态资源文件:
这里直接使用前一节的代码啦~
// index.js 开发 graphql 接口
//...省略其余
const schema = buildSchema(` type Hero { name: String age: Int doSomething(thing: String): String } type Query { getSuperHero(heroName: String!): Hero } `)
const root = {
getSuperHero: ({heroName}) => {
// 这里的操做 实际开发中经常用在请求数据库
const name = heroName
const age = 46
const doSomething = ({thing}) => {
return `I'm ${name}, I'm ${thing} now`
}
return { name, age, doSomething }
}
}
const app = express()
app.use('/graphql', graphqlHTTP({
schema, rootValue: root, graphiql: true
}))
// 公开文件夹 使用户访问静态资源
app.use(express.static('public'))
app.listen(3000)
复制代码
这样咱们就给前端页面提供一个能够访问静态资源的功能。
这里还须要在根目录建立一个 public 文件夹,并在文件夹中添加 index.html
文件,此时的目录结构:
|-node_modules
|-public
|---index.html
|-index.js
|-package.json
复制代码
而后给 index.html
添加按钮和事件绑定:
这里的变量 query
是个字符串类型,定义查询条件,在条件 GetSuperHero
中的参数,须要用 $
符号来标识,并在实际查询 getSuperHero
中,做为参数的参数类型设置进来。
而后定义变量 variables
,指定属性的值,以后经过 fetch
发起请求:
<button onclick="getData()">获取数据</button>
<script> function getData(){ const query = ` query GetSuperHero($heroName: String, $thing: String){ getSuperHero(heroName: $heroName){ name age doSomething(thing: $thing) } } ` // 若是不须要其余参数 至少要传一个参数 不然会报错 // const query = ` // query GetSuperHero($heroName: String){ // getSuperHero(heroName: $heroName){ // name // } // } // ` const variables = {heroName: '钢铁侠', thing: 'watching TV'} fetch('./graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ query, variables }) }) .then(res => res.json()) .then(json => { console.log(json) }) } </script>
复制代码
当咱们写完之后,点击 获取数据 就会在控制台打印下面的数据:
{
"data":{
"getSuperHero":{
"name":"钢铁侠",
"age":46,
"doSomething": "I'm 钢铁侠, I'm watching TV now"
}
}
}
复制代码
query
参数须要对照好有 $
符号的变量。查询语句 query GetSuperHero($heroName: String)
里参数 $heroName
中的 heroName
;
查询语句 getSuperHero(heroName: $heroName)
里类型 $heroName
中的 heroName
;
变量 variables
中的 heroName
属性;
这三个名称须要同样。
body: JSON.stringify({ query, variables })
复制代码
根据前面的学习,咱们知道,要作查询操做,须要使用 Query
来声明:
type Query {
queryHero(heroName: String): String
}
复制代码
当咱们要作修改操做,须要用到的是 Mutation
:
type Mutation {
createHero(heroName: String): String
}
复制代码
若是 Mutation
中字段的形参是自定义类型,则类型须要用 input
标识:
const schema = buildSchema(` # 输入类型 用 input 标识 input HeroInput { name: String age: Int } # 查询类型 type Hero { name: String age: Int } type Mutation { createHero(heroName: String): Hero updateHero(heroName: String, hero: HeroInput): Hero } `)
复制代码
注意下:这里须要至少定义一个 Query
否则GraphiQL
会不显示查询:
type Query {
hero: [Hero]
}
复制代码
先建立一个 schema
,内容为上一步【1. Mutation 使用】中定义的内容,这里不重复写。
而后模拟建立一个本地数据库 localDb
, 用于模拟存放添加的超级英雄数据:
const localDb = {}
复制代码
接下来声明 root
实现 schema
中的字段方法:
const root = {
hero() {
// 这里须要转成数组 由于前面定义了返回值是 [Hero] 类型
let arr = []
for(const key in localDb){
arr.push(localDb[key])
}
return arr
},
createHero({ input }) {
// 至关于数据库的添加操做
localDb[input.name] = input
return localDb[input.name]
},
updateHero({ id, input }) {
// 至关于数据库的更新操做
const update = Object.assign({}, localDb[id], input)
localDb[id] = update
return update
}
}
复制代码
最后配置 graphqlHTTP
方法和启动服务器,这里就很少重复咯。
最终代码:
//...省略其余
const schema = buildSchema(` # 输入类型 用 input 标识 input HeroInput { name: String age: Int } # 查询类型 type Hero { name: String age: Int } type Mutation { createHero(input: HeroInput): Hero updateHero(id: ID!, input: HeroInput): Hero } # 须要至少定义一个 Query 不要GraphiQL会不显示查询 type Query { hero: [Hero] } `)
const localDb = {}
const root = {
hero() {
// 这里须要转成数组 由于前面定义了返回值是 [Hero] 类型
let arr = []
for(const key in localDb){
arr.push(localDb[key])
}
return arr
},
createHero({ input }) {
// 至关于数据库的添加操做
localDb[input.name] = input
return localDb[input.name]
},
updateHero({ id, input }) {
// 至关于数据库的更新操做
const update = Object.assign({}, localDb[id], input)
localDb[id] = update
return update
}
}
//...省略其余
复制代码
如今咱们能够启动服务器,在 GraphiQL
上测试下效果了。
咱们是使用 mutation
的 createHero
字段添加两条数据:
mutation {
createHero(input: {
name: "钢铁侠"
age: 40
}){
name
age
}
}
复制代码
mutation {
createHero(input: {
name: "美国队长"
age: 41
}){
name
age
}
}
复制代码
而后使用 query
的 hero
字段查询添加的结果:
query {
hero {
name
age
}
}
复制代码
这样咱们就获取到刚才的添加结果:
{
"data": {
"hero": [
{
"name": "钢铁侠",
"age": 40
},
{
"name": "美国队长",
"age": 41
}
]
}
}
复制代码
而后咱们开始更新数据,使用 mutation
的 updateHero
字段将 美国队长 的 age
值修改成 18:
mutation {
updateHero(id: "美国队长", input: {
age: 18
}){
age
}
}
复制代码
再使用 query
的 hero
字段查询下新的数据,会发现 美国队长 的 age
值已经更新为 18:
{
"data": {
"hero": [
{
"name": "钢铁侠",
"age": 40
},
{
"name": "美国队长",
"age": 18
}
]
}
}
复制代码
咱们知道,修改数据的接口不能让全部人随意访问,因此须要添加权限认证,让有权限的人才能够访问。
在 express
中,能够很简单的使用中间件来将请求进行拦截,将没有权限的请求过滤并返回错误提示。
中间件其实是一个函数,在接口执行以前,先拦截请求,再决定咱们是否接着往下走,仍是返回错误提示。
这在【6、使用Mutations修改数据】的最终代码上,在添加这个中间件:
//... 省略其余
const app = express()
const middleWare = (req, res, next) => {
// 这里是简单模拟权限
// 实际开发中 更多的是和后端进行 token 交换来判断权限
if(req.url.indexOf('/graphql') !== -1 && req.headers.cookie.indexOf('auth') === -1){
// 向客户端返回一个错误信息
res.send(JSON.stringify({
err: '暂无权限'
}))
return
}
next() // 正常下一步
}
// 注册中间件
app.use(middleWare)
//... 省略其余
复制代码
这里的权限判断,只是简单模拟,实际开发中,更多的是和后端进行 token
交换来判断权限(或者其余形式)。
咱们重启服务器,打开 http://localhost:3000/graphql
,发现页面提示错误了,由于 cookies
中没有含有 auth
字符串。
若是这里提示 TypeError: Cannot read property 'indexOf' of undefined
,能够先不用管,由于浏览器中没有 cookies
的缘由,其实前面的权限判断逻辑须要根据具体业务场景判断。
为了方便测试,咱们在 chrome 浏览器控制台的 application
下,手动设置一个含有 auth
字符串的一个 cookies
,只是测试使用哦。
设置完成后,咱们就能正常进入页面。
在前面的介绍中,咱们要建立一个 schema
都是使用 buildSchema
方法来定义,但咱们也可使用另一种定义方式。
就是这里要学习使用的构造函数 graphql.GraphQLObjectType
定义,它有这么几个优势和缺点:
这里先将前面定义的 Hero
类型进行改造:
const graphql = require('graphql') // 须要引入
const HeroType = new graphql.GraphQLObjectType({
name: 'Hero',
fields: {
name:{ type: graphql.GraphQLString },
age:{ type: graphql.GraphQLInt },
}
})
复制代码
二者区别在于:
区别 | buildSchema |
graphql.GraphQLObjectType |
---|---|---|
参数类型 | 字符串 | 对象 |
类名 | 跟在 type 字符后面,这里是 type Hero |
在参数对象的 name 属性上 |
属性定义 | 定义在类型后,键值对形式 | 定义在参数对象 fields 属性中,值为对象,每一个属性名为键名,值也是对象,其中 type 属性的值为 graphql 中的属性,下面会补充 |
补充:
fields
属性中的子属性的类型一般有:
graphql.GraphQLString
graphql.GraphQLInt
graphql.GraphQLBoolean
....即在 GraphQL
后面跟上基本类型名称。
定义查询的时候,跟以前相似,能够参照下面对比图理解,这里比较不一样的是,多了个 resolve
的方法,这个方法是用来执行处理查询的逻辑,其实就是以前的 root
中的方法。
const QueryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
// 一个个查询方法
getSuperHero: {
type: HeroType,
args: {
heroName: { type: graphql.GraphQLString }
},
// 方法实现 查询的处理函数
resolve: function(_, { heroName }){
const name = heroName
const age = 18
return { name, age }
}
}
}
})
复制代码
建立的时候只需实例化而且将参数传入便可:
// step3 构造 schema
const schema = new graphql.GraphQLSchema({ query: QueryType})
复制代码
最后使用也是和前面同样:
const app = express()
app.use('/graphql', graphqlHTTP({
schema,
graphiql: true
}))
app.listen(3000)
复制代码
咱们试着使用前面所学的内容,开发一个简单的实践项目:
经过 GraphiQL
页面,往 Mongodb
中插入和更新数据,主要用到【6、使用Mutations修改数据】章节的操做。
首先咱们能够到 Mongodb 官网 选择对应平台和版本下载安装。
下载安装步骤,能够参考 mongoDB下载、安装和配置,这里就很少介绍哟~~
安装完成后,咱们打开两个终端,分别执行下面两行命令:
// 终端1 启动数据库
mongod --dbpath c:\leo\app\mongodb\data\db
// 终端2 进入数据库命令行操做模式
mongo
复制代码
首先咱们新建一个文件 db.js
,并 npm install mongoose
安装 mongoose
,而后写入下面代码,实现链接数据库:
const express = require('express')
const { buildSchema } = require('graphql')
const graphqlHTTP = require('express-graphql')
const mongoose = require('mongoose')
const DB_PATH = 'mongodb://127.0.0.1:27017/hero_table'
const connect = () => {
// 链接数据库
mongoose.connect(DB_PATH)
// 链接断开
mongoose.connection.on('disconnected', () => {
mongoose.connect(DB_PATH)
})
// 链接失败
mongoose.connection.on('error', err => {
console.error(err)
})
// 链接成功
mongoose.connection.on('connected', async () => {
console.log('Connected to MongoDB connected', DB_PATH)
})
}
connect()
复制代码
而后建立 Schema
和 Model
:
let HeroSchema = new mongoose.Schema({
name: String,
age: Number
})
let HeroModel = mongoose.model('hero',HeroSchema, 'hero_table')
复制代码
这一步,仍是先使用【6、使用Mutations修改数据】章节的操做逻辑,也就是先用字符串建立查询,而不使用 GraphQLObjectType
建立:
const schema = buildSchema(` # 输入类型 用 input 标识 input HeroInput { name: String age: Int } # 查询类型 type Hero { name: String age: Int } type Mutation { createHero(input: HeroInput): Hero updateHero(hero: String!, input: HeroInput): Hero } # 须要至少定义一个 Query 不要GraphiQL会不显示查询 type Query { hero: [Hero] } `)
复制代码
这边案例有稍做修改
这边处理添加数据和更新数据的逻辑,就要修改以前声明的 root
的操做内容了:
const root = {
hero() {
return new Promise( (resolve, reject) => {
HeroModel.find((err, res) => {
if(err) {
reject(err)
return
}
resolve(res)
})
})
},
createHero({ input }) {
// 实例化一个Model
const { name, age } = input
const params = new HeroModel({ name, age })
return new Promise( (resolve, reject) => {
params.save((err, res) => {
if(err) {
reject(err)
return
}
resolve(res)
})
})
},
updateHero({ hero, input }) {
const { age } = input
return new Promise ((resolve, reject) => {
HeroModel.update({name: hero}, {age}, (err, res) => {
if(err) {
reject(err)
return
}
resolve(res)
})
})
}
}
复制代码
最后咱们在 GraphiQL
页面上模拟测试一下,首先添加两个英雄,钢铁侠和美国队长,并设置他们的 age / name
属性:
mutation {
createHero(input: {
name: "钢铁侠"
age: 40
}){
name
age
}
}
复制代码
mutation {
createHero(input: {
name: "美国队长"
age: 20
}){
name
age
}
}
复制代码
页面和接口没有报错,说明咱们添加成功,数据库中也有这两条数据了:
在测试下查询:
query {
hero {
name
age
}
}
复制代码
查询也正常,接下来测试下更新,将美国队长的 age
修改成 60:
mutation {
updateHero(hero: "美国队长", input: {
age: 60
}){
age
}
}
复制代码
到这一步,咱们也算是将这个练习作完了。
GraphQL
是一种 API 的查询语言,是 REST API 的替代品。
GraphQL
可使用一个请求,获取全部想要的数据。
建立查询的方式有两种:使用 buildSchema
或者 GraphQLObjectType
。
查询操做用 Query
,修改操做用 Mutations
。
查询类型用 type
,输入类型用 input
。
其实 GraphQL
仍是很简单好用的呢~~~
本文首发在 pingan8787我的博客,如需转载请保留我的介绍