GraphQL
官方描述:
GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
优点
- GraphQL可以让我们通过请求控制返回的字段,以此来减少restful api的设计理念带来的请求多次的问题。
比如我们要获取指定id的文章相关信息,包括标题、作者、发布时间以及前两条评论;同时加载当前用户信息。
// 两趟查询,难以拓展
GET /user/111
GET /article/1001?comment=2
// 一趟查询,易于扩展
{
article (id: 1001){
title,
author,
time,
comments (first: 2)
nickname,
time,
content
}
},
user (id: 111){
nickname,
photo,
sign
}
}
- 可拓展性
前端自由选择返回 - 不需要额外代码处理冗余字段
- 提供 schema
schema 可以在运行时被获取到, 类似thrift的idl文件,比较清楚,简单的方法甚至不需要文档。 - 调试比较方便
缺点
- 缓存麻烦
https://graphql.cn/learn/caching/
Relay 和 apollo-client 是两个graphql的前端模块,可以帮你在前端缓存数据。 - GraphQL 在前端如何与视图层、状态管理方案结合,目前也只有 React/Relay 这个一个官方方案。
换句话说,如果你不是已经在用 Node + React 这个技术栈,引入 GraphQL 成本略高,风险也不小,这就很大程度上限制了受众。
而且 FB 官方就只有一个 Node.js 的 reference implementation,其他语言都是社区做的。 - 每一个 field 都对数据库直接跑一个 query,会产生大量冗余 query,虽然网络层面的请求数被优化了,但数据库查询可能会成为性能瓶颈。
FB 本身没有这个问题,因为他们内部数据库这一层也是抽象掉的,写 GraphQL 接口的人不需要顾虑 query 优化的问题。
如何解决?
DataLoader, DataLoader 的主要功能是 batching & caching,帮你合并请求。 - 需要服务端的全力配合
- GraphQL不存在鉴权方案,需要自行解决
get start
GraphQL为express和koa提供了插件,可以方便的搭建GraphQL服务器。
看下面的代码:
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// 使用 GraphQL schema language 构建 schema
var schema = buildSchema(`
input MessageInput {
content: String
author: String
}
type Message {
id: ID!
content: String
author: String
}
type Query {
getMessage(id: ID!): Message
}
type Mutation {
createMessage(input: MessageInput): Message
updateMessage(id: ID!, input: MessageInput): Message
}
`);
// 如果 Message 拥有复杂字段,我们把它们放在这个对象里面。
class Message {
constructor(id, {content, author}) {
this.id = id;
this.content = content;
this.author = author;
}
}
// 映射 username 到 content
var fakeDatabase = {};
var root = {
getMessage: function ({id}) {
if (!fakeDatabase[id]) {
throw new Error('no message exists with id ' + id);
}
return new Message(id, fakeDatabase[id]);
},
createMessage: function ({input}) {
// Create a random id for our "database".
var id = require('crypto').randomBytes(10).toString('hex');
fakeDatabase[id] = input;
return new Message(id, input);
},
updateMessage: function ({id, input}) {
if (!fakeDatabase[id]) {
throw new Error('no message exists with id ' + id);
}
// This replaces all old data, but some apps might want partial update.
fakeDatabase[id] = input;
return new Message(id, input);
},
};
var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(4000, () => {
console.log('Running a GraphQL API server at localhost:4000/graphql');
});
我们通过GraphQL提供的语法,建立一个schema,类似typescript的interface。
有多种基本类型:ID, String, Int, []。
Query是查询操作,Mutation是增删改操作。
所以这里,我们提供了一个getMessage的方法,返回Message类型的信息。
Mutation类型这里我们提供了createMessage,和updateMessage两个方法。
定义完schema,还需要定义方法的处理:
定义root对象进行函数处理。
执行node index.js
。然后访问localhost:4000/graphql就可以看到相应的调试页面。(前提是graphiql:true)。
这个调试页面还是很方便的。
打开调试页面,先create一个message:
然后查询这个message:
type 为query的时候,可以不写query。
前端请求
var dice = 1;
var sides = 6;
var query = `query RollDice($dice: Int!, $sides: Int) {
rollDice(numDice: $dice, numSides: $sides)
}`;
fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query,
variables: { dice, sides },
})
})
.then(r => r.json())
.then(data => console.log('data returned:', data));
这是官网给的试例。fetch请求,把query传过去就可以啦,如果请求的是个函数,需要传参variables。如上dice和sides两个参数
连接数据库
这里给个连接mysql的schema和rootValue
var { buildSchema } = require('graphql');
const mysqlCon = require('./mysql');
// 使用 GraphQL schema language 构造一个 schema
let count = 0;
var schema = buildSchema(`
type UserInfo {
id: ID!
name: String
uid: Int
age: Int
sex: String
createdTime: String
updatedTime: String
description: String
}
type Query {
getUsers: [UserInfo]
getUserById(id: ID!): UserInfo
}
type Mutation {
invokeCount: Int
}
`);
// root 为每个端点入口 API 提供一个解析器
var root = {
async getUsers() {
count += 1;
let users = await mysqlCon.pifySelect('select * from Tab_User_Info');
console.log(users);
return users;
},
async getUserById({id}) {
let users = await mysqlCon.pifySelect(`select * from Tab_User_Info where id=${id}`)
return users[0];
},
invokeCount() {
return count;
}
};
module.exports = {
root,
schema,
}
mysql.js
const mysql = require('mysql');
const connection = mysql.createConnection({
host: '127.0.0.1',
port: 3306,
user: 'root',
password: '123456',
database: 'Test_User'
});
connection.connect(function(err) {
if (err) {
console.error('error: ' + err.stack);
return;
}
});
Object.defineProperty(connection, 'pifySelect', {
value: function(sql) {
return new Promise((resolve, _)=>{
connection.query(sql, function (error, results) {
if (error) console.log('mysql select err:', error);
resolve(results);
});
})
}
})
module.exports = connection
用docker搞个mysql环境:
docker pull mysql:5.6
docker run -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.6
docker exec -it mysql bash
进入bash然后初始化一下mysql,插几个数据用来操作。
用koa-graphql或者express-graphql启一个服务,就可以直接访问啦。
使用方
facebook, twitter,github,我们公司(toutiao)部分业务在用。