六、使用Mutations修改数据
1. Mutation 使用
根据前面的学习,我们知道,要做查询操作,需要使用 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] }
2. Mutation 使用案例
先创建一个 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
中,可以很简单的使用中间件来将请求进行拦截,将没有权限的请求过滤并返回错误提示。
中间件实际上是一个函数,在接口执行之前,先拦截请求,再决定我们是否接着往下走,还是返回错误提示。
这在【六、使用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
,只是测试使用哦。
设置完成后,我们就能正常进入页面。
八、ConstructingTypes
在前面的介绍中,我们要创建一个 schema
都是使用 buildSchema
方法来定义,但我们也可以使用另外一种定义方式。
就是这里要学习使用的构造函数 graphql.GraphQLObjectType
定义,它有这么几个优点和缺点:
- 优点:报错提醒更直观,结构更清晰,更便于维护。
- 缺点:代码量上升。
1. 定义type(类型)
这里先将前面定义的 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
后面跟上基本类型名称。
2. 定义query(查询)
定义查询的时候,跟之前类似,可以参照下面对比图理解,这里比较不同的是,多了个 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 } } } } })
3. 创建 schema
创建的时候只需实例化并且将参数传入即可:
// step3 构造 schema const schema = new graphql.GraphQLSchema({ query: QueryType})
最后使用也是和前面一样:
const app = express() app.use('/graphql', graphqlHTTP({ schema, graphiql: true })) app.listen(3000)
九、与数据库结合实战
我们试着使用前面所学的内容,开发一个简单的实践项目:
通过 GraphiQL
页面,往 Mongodb
中插入和更新数据,主要用到【六、使用Mutations修改数据】章节的操作。
1. 搭建并启动本地 Mongodb 数据库
首先我们可以到 Mongodb 官网 选择对应平台和版本下载安装。
下载安装步骤,可以参考 mongoDB下载、安装和配置,这里就不多介绍哟~~
安装完成后,我们打开两个终端,分别执行下面两行命令:
// 终端1 启动数据库 mongod --dbpath c:\leo\app\mongodb\data\db // 终端2 进入数据库命令行操作模式 mongo
2. 连接数据库,创建 Schema 和 Model
首先我们新建一个文件 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')
3. 声明查询语句
这一步,还是先使用【六、使用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] } `)
这边案例有稍作修改
4. 实现添加数据和更新数据的逻辑
这边处理添加数据和更新数据的逻辑,就要修改之前声明的 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) }) }) } }
5. 模拟测试
最后我们在 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个人博客,如需转载请保留个人介绍