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)部分業務在用。