GraphQL 简介

一种用于 API 的查询语言。

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

对比 restful api:

  • restful —个接口只能返回一个资源, graphql 一次可以获取多个资源。
  • restful 用不同的 url 来区分资源, graphql 用类型区分资源。

安装

使用 JavaScript 语言,express node.js 框架开始。

npm install express express-graphql graphql

创建 server.js 并 使用命令 node server.js 运行 demo。

const express = require('express');
const app = express();
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql'); const schema = buildSchema(`
type Query {
hello: String
}
`); const root = { hello: () => 'Hello world!' }; app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'));

参数类型

  • 基本类型: String, Int, Float, Boolean, ID
  • [类型]代表数组,例如:[Int]代表整型数组。

参数传递

  • 和 JavaScript 传递参数一样,小括号内定义形参,但是注意 : 参数需要定义类型。
  • ! 叹号代表参数不能为空。
type Query {
rollDice (numDice: Int!, numSide: Int): [Int]
}

自定义参数类型

GraphQL 允许用户自定义参数类型,通常用来描述要获取的资源的属性。

type Account {
name: String
age: Int
sex: String
department: String
salary: (city: String): Int
} type Query {
account (name: string): Account
}

server.js

const schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
salary(city: String): Int
} type Query {
hello: String
getClassMates(classNo: Int!): [String]
account (username: String): Account
}
`) const root = {
hello: () => 'Hello world!', getClassMates: ({ classNo }) => {
const obj = {
31: ['张三', '李四', '王五'],
61: ['张大三', '李大四', '王大五'],
}
return obj[classNo]
}, account: ({ username }) => {
const name = username
const sex = 'nan'
const age = 10
const department = '开发部'
const salary = ({ city }) => {
if (city === '北京' || city === '上海' || city === '深圳' || city === '广州') {
return 10000
}
return 3000
}
return {
name,
sex,
age,
department,
salary,
}
},
}

在客户端访问GraphQL接口

在 server.js 公开文件夹 供用户访问静态资源。

app.use(express.static('public'))

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body> <button onclick="getData()">获取数据</button> <script> function getData() {
const query = `
query Account ($username: String!) {
account(username: $username) {
name
age
sex
salary(city: "北京")
}
}` const variables = { username: '李大四' } 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>
</body>
</html>

后端的 username 对应 variales 的值中的 username, 和 query 中的 $username

使用 Mutations 修改数据

不能单独使用 Mutation, 需结合 Query


const schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
salary(city: String): Int
} input AccountInput {
name: String
age: Int
sex: String
department: String
salary: Int
} type Query {
accounts: [Account]
} type Mutation {
createAccount(input: AccountInput): Account
updateAccount(id: ID!, input: AccountInput): Account
}
`) // 模拟数据库
const fakeDb = {} const root = {
createAccount({ input }) {
// 模拟数据库保存
fakeDb[input.name] = input
// 返回保存结果
return fakeDb[input.name]
}, updateAccount({ id, input }) {
// 模拟更新数据库
const updatedAccount = Object.assign({}, fakeDb[id], input)
fakeDb[id] = updatedAccount
return updatedAccount
}, accounts() {
let arr = []
for (const key in fakeDb) {
arr.push(fakeDb[key])
}
return arr
},
}

试着创建一个 account。

通过 accounts,查询到刚刚创建的 account。

试着修改数据,我们将 age 18 改为 20,并将该 account 更改后的信息返回。

这里将 name 作为记录的主键了。

Constructing Types

上文通过字符串的形式构建 schema,还可以通过构造函数来构建。

以前:

const schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
salary(city: String): Int
} type Query {
account (username: String): Account
}
`)

现在

const AccountType = new graphql.GraphQLObjectType({
name: 'Account',
fields: {
name: {type: graphql.GraphQLString},
age: {type: graphiql.GraphQLInt},
sex: {type: raphql.GraphQLString},
department: {type: graphql.GraphQLString},
}
}) const queryType = new graphiql.GraphQLObjectType({
name: 'Query',
fields: {
account: {
type: AccountType,
args: {
username: {type: graphiql.GraphQLString}
},
resolve(_, {username}){
const name = username
const sex = 'nan'
const age = 18
const department = '开发部'
return {
name,
age,
sex,
department
}
}
}
}
}) const schema = new graphiql.GraphQLSchema({query: queryType})

代码量提升,编辑器提示,可维护性提升,报错信息更精准。

结合 MySql CURD

接下来需要稍作更改,拼接几个 SQL 语句, 操作数据库。

创建数据库 test , 表 account,并添加几个字段如下:

npm i mysql -S
const mysql = require('mysql')

const pool = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
user: 'root',
password: '123456',
database: 'test',
}) const schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
salary(city: String): Int
} input AccountInput {
name: String
age: Int
sex: String
department: String
salary: Int
} type Query {
accounts: [Account]
} type Mutation {
createAccount(input: AccountInput): Account
updateAccount(id: ID!, input: AccountInput): Account
deleteAccount(id: ID!): Boolean
}
`) const rootValue = {
createAccount({ input }) {
const data = {
name: input.name,
age: input.age,
sex: input.sex,
department: input.department,
} // query 是异步操作, 使用 promise
return new Promise((resolve, reject) => {
pool.query('insert into account set ?', data, err => {
if (err) {
console.log('出错了' + err.message)
return
}
resolve(data)
})
})
}, deleteAccount({ id }) {
return new Promise((resolve, reject) => {
pool.query('delete from account where id = ?', id, err => {
if (err) {
console.log('出错了' + err)
reject(false)
return
}
resolve(true)
})
})
}, updateAccount({ id, input }) {
const data = input
return new Promise((resolve, reject) => {
pool.query('update account set ? where id = ?', [data, id], err => {
if (err) {
console.log('出错了' + err.message)
return
}
resolve(data)
})
})
}, accounts() {
return new Promise((resolve, reject) => {
pool.query('select name, age, sex, department from account', (err, res) => {
if (err) {
console.log('出错了' + err)
return
}
let arr = []
for (let i = 0; i < res.length; i++) {
arr.push({
name: res[i].name,
sex: res[i].sex,
age: res[i].age,
department: res[i].department,
})
}
resolve(arr)
})
})
},
}

代码片段

为方便操作,我将完整代码片段放在最后,供你试一试。

fakeDb
const express = require('express')
const app = express()
const { graphqlHTTP } = require('express-graphql')
const { buildSchema } = require('graphql') const schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
salary(city: String): Int
} input AccountInput {
name: String
age: Int
sex: String
department: String
salary: Int
} type Query {
hello: String
getClassMates(classNo: Int!): [String]
account (username: String): Account
accounts: [Account]
} type Mutation {
createAccount(input: AccountInput): Account
updateAccount(id: ID!, input: AccountInput): Account
}
`) const fakeDb = {} const rootValue = {
hello: () => 'Hello world!', getClassMates: ({ classNo }) => {
const obj = {
31: ['张三', '李四', '王五'],
61: ['张大三', '李大四', '王大五'],
}
return obj[classNo]
}, account: ({ username }) => {
const name = username
const sex = 'nan'
const age = 10
const department = '开发部'
const salary = ({ city }) => {
if (city === '北京' || city === '上海' || city === '深圳' || city === '广州') {
return 10000
}
return 3000
}
return {
name,
sex,
age,
department,
salary,
}
}, createAccount({ input }) {
// 模拟数据库保存
fakeDb[input.name] = input
// 返回保存结果
return fakeDb[input.name]
}, updateAccount({ id, input }) {
// 模拟更新数据库
const updatedAccount = Object.assign({}, fakeDb[id], input)
fakeDb[id] = updatedAccount
return updatedAccount
}, accounts() {
let arr = []
for (const key in fakeDb) {
arr.push(fakeDb[key])
}
return arr
},
} // 公开文件夹 供用户访问静态资源
app.use(express.static('public')) // const middleware = (req, res, next) => {
// // console.log(req.headers.cookie)
// if (req.url.indexOf('/graphql') !== -1) {
// res.send(
// JSON.stringify({
// error: '您没有权访问这个接口',
// })
// )
// return
// }
// next()
// } // app.use(middleware) app.use(
'/graphql',
graphqlHTTP({
schema,
rootValue,
graphiql: true,
})
) app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'))
MySQL
const express = require('express')
const app = express()
const { graphqlHTTP } = require('express-graphql')
const { buildSchema } = require('graphql')
const mysql = require('mysql')
const { resolve } = require('path') const pool = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
user: 'root',
password: '123456',
database: 'test',
}) const schema = buildSchema(`
type Account {
name: String
age: Int
sex: String
department: String
salary(city: String): Int
} input AccountInput {
name: String
age: Int
sex: String
department: String
salary: Int
} type Query {
hello: String
getClassMates(classNo: Int!): [String]
account (username: String): Account
accounts: [Account]
} type Mutation {
createAccount(input: AccountInput): Account
updateAccount(id: ID!, input: AccountInput): Account
deleteAccount(id: ID!): Boolean
}
`) const rootValue = {
hello: () => 'Hello world!', getClassMates: ({ classNo }) => {
const obj = {
31: ['张三', '李四', '王五'],
61: ['张大三', '李大四', '王大五'],
}
return obj[classNo]
}, account: ({ username }) => {
const name = username
const sex = 'nan'
const age = 10
const department = '开发部'
const salary = ({ city }) => {
if (city === '北京' || city === '上海' || city === '深圳' || city === '广州') {
return 10000
}
return 3000
}
return {
name,
sex,
age,
department,
salary,
}
}, accounts() {
return new Promise((resolve, reject) => {
pool.query('select name, age, sex, department from account', (err, res) => {
if (err) {
console.log('出错了' + err)
return
}
let arr = []
for (let i = 0; i < res.length; i++) {
arr.push({
name: res[i].name,
sex: res[i].sex,
age: res[i].age,
department: res[i].department,
})
}
resolve(arr)
})
})
}, createAccount({ input }) {
const data = {
name: input.name,
age: input.age,
sex: input.sex,
department: input.department,
}
return new Promise((resolve, reject) => {
pool.query('insert into account set ?', data, err => {
if (err) {
console.log('出错了' + err.message)
return
}
resolve(data)
})
})
}, updateAccount({ id, input }) {
const data = input
return new Promise((resolve, reject) => {
pool.query('update account set ? where id = ?', [data, id], err => {
if (err) {
console.log('出错了' + err.message)
return
}
resolve(data)
})
})
}, deleteAccount({ id }) {
return new Promise((resolve, reject) => {
pool.query('delete from account where id = ?', id, err => {
if (err) {
console.log('出错了' + err)
reject(false)
return
}
resolve(true)
})
})
},
} // 公开文件夹 供用户访问静态资源
app.use(express.static('public')) app.use(
'/graphql',
graphqlHTTP({
schema,
rootValue,
graphiql: true,
})
) app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'))

试一试 GraphQL的更多相关文章

  1. 仿造w3school的试一试功能,实现左侧编辑框,右侧效果页面

    转自http://fhqllt.iteye.com/blog/836186 每次想快速测试页面效果的时候,特别是在学习前端代码的时候,就想到W3school的那个试一试功能,一直都是用他们那个在线的版 ...

  2. Python 从入门到实践 试一试 参考代码

    这两天学习Python 看了python从入门到实践的书籍,里面有课后题“试一试” 然后就跟着写了,代码在以下地址,如果需要自取 https://files.cnblogs.com/files/fud ...

  3. Python编程-从入门到实践 Eric Matthes 著 袁国忠 译 - - 第二章 动手试一试

    因为第一章的动手试一试基本都是探索性的,所以直接进入第二章. # 2.2 动手试一试 # 2_1 简单消息: 将一条消息存储到变量中,再将其打印出来. message = 'python 编程从入门到 ...

  4. 简单的试了试async和await处理异步的方式

    今天无意中就来试了试,感觉这个新的方法还是非常行的通的,接下来我们上代码 这段代码想都不用想输出顺序肯定是//null null 233,当然出现这个问题还是因为它是同步,接下来我们就进行异步方式来处 ...

  5. 试来试去,WIN下最简单的WIN API开发工具,Pelles C就好啦

    昨晚试过N个,不是太大,就是不容易和WIN API集成. 今早一试就灵了个.... Pelles C. Pelles C是一款windows下的C IDE,支持调试,且为免费.它有一个高效率的链接器, ...

  6. myeclipse激活法,可以试一试

    我的myeclipse2014也是这样激活: 第一步:输入任意用户名 第二步:点击Systemid... 按钮,自动生成本机器的systemid. 第三步: 点菜单Tools->RebuildK ...

  7. 关于w3school的html5部分output 元素实例代码(点亲自试一试进去)的问题纠正

    修复: 将原来的 =  号修改成 <input type="button" onclick="resCalc()" value ="=" ...

  8. C#实现Dll(OCX)控件自动注册的两种方法 网上找的 然后 自己试了试 还是可以用的

    尽管MS为我们提供了丰富的.net framework库,我们的程序C#开发带来了极大的便利,但是有时候,一些特定功能的控件库还是需要由第三方提供或是自己编写.当需要用到Dll引用的时候,我们通常会通 ...

  9. Python自学:第三章 动手试一试 3-4、3-5

    # -*- coding: GBK -*- liebiao = ["zhang", "li", "wang", "zhou&quo ...

随机推荐

  1. Java操作RockeMQ

    RocketMQ是阿里巴巴在2012年开源的分布式消息中间件,目前已经捐赠给Apache基金会,已经于2016年11月成为 Apache 孵化项目,相信RocketMQ的未来会发挥着越来越大的作用,将 ...

  2. SSM中保存数据出现415错误

    服务器415错误 ssm框架的controller jsp页面 问题:页面出现415错误 原因:请求和响应类型不一致 分析: 先排除以下基本的环境配置 1.URL路径对应好,视图解析器配置好,cont ...

  3. 解决UEditor编辑时,只添加视频内容,不添加文字,视频信息不能保存到数据库的问题

    造成这个问题的原因是富文本保存内容时会筛除空标签,然后统计是否有内容,通过字数统计也可以看到,上传完视频后字数还是零,因为视频上传后是<video></video>标签,这个标 ...

  4. 【反转开灯问题】Face The Right Way

    题目 Farmer John has arranged his N (1 ≤ N ≤ 5,000) cows in a row and many of them are facing forward, ...

  5. 基于 fetch 的请求封装

    原生 fetch 请求失败后(如无网络)状态会变成 reject 走 .catch .绝大多数情况下业务场景只需要给个 toast 等简单处理.每个请求都 .catch 会显得格外繁琐,并且如果不 . ...

  6. 《UNIX环境高级编程》(APUE) 笔记第四章 - 文件和目录

    4 - 文件和目录 1. 函数 stat.fstat.fstatat 和 lstat #inlcude <sys/stat.h> int stat(const char *restrict ...

  7. 如何解决TOP-K问题

    前言:最近在开发一个功能:动态展示的订单数量排名前10的城市,这是一个典型的Top-k问题,其中k=10,也就是说找到一个集合中的前10名.实际生活中Top-K的问题非常广泛,比如:微博热搜的前100 ...

  8. sendRedirect()和forward()方法的区别

    sendRedirect()和forward()方法的区别 之前好像曾经整理过,但忘了放在哪里了,好像压根就没整理过,博客里也没有,故今天重新整理一下. 我们知道页面之间的跳转有两种方式:重定向与转发 ...

  9. 彻底解决安卓7.0及以上版本抓包https失败

    目录 现象 原因 解决办法 webview抓包失败 警告 现象 android7.0以上的手机https抓包失败(安装了https证书也不行) 原因 android7.0+的版本新增了证书验证(系统证 ...

  10. Python趣味入门4:选择往往是最重要的-条件语句

    人生处处有选择,程序也有选择,为了让程序变得更加强壮,程序员必须考虑任何情况,上一篇了解到了如何使用Python来行顺序语句的编写,我们写了一个可以输入姓名的生日祝贺程序,今天我们挑战条件语句! 1. ...