GraphQL - arthur791004/notes GitHub Wiki
Why
REST 的問題
- 參數、回傳值型別不固定
- content-type:
x-form-www-urlencoded
,json
,form-data
- return format: number? string?
- content-type:
- 多版本時,前後端常不匹配
- 會拿多餘的欄位 (浪費頻寬)
- 通常會回傳全部的資訊,但其實有些並不需要
- 可加上 field, ignore 之類的參數,But 非標準
- 不容易處理巢狀資源
- REST 的目標是去處理單一資源,處理資源之間的關聯就變成一個灰色地帶。
- Example: 拿到某篇文章的作者資訊
GET /posts/1 GET /users/arthur
- 會不斷成長的 Endpoint 數量
- 需要良好的設計來避免
GraphQL
參數、回傳值型別不固定- 必須定義型別,所有值都必須通過 Validation
多版本時,前後端常不匹配- 前後端共用一個固定的 Schema
會拿多餘的欄位- client 自行定義所需要的欄位
不容易處理巢狀資源- 要巢狀幾層都可以
{ post(id: "1") { title author { id name posts { title } } } }
會不斷成長的 Endpoint 數量- 永遠都只有一個 Endpoint,來處理所有的查詢。
普及率
- Facebook, Twitter, Github, Pinterest, Coursera, Yoctol, Appier, Fandora
Setup
Installation
yarn add graphql express-graphql express
Quick Start
// GraphQLSchema.js
const { buildSchema } = require('graphql');
exports.schema = buildSchema(`
type Query {
hello: String
}
`);
exports.rootValue = {
hello: () => 'Hello world!',
};
// server.js
const express = require('express');
const graphqlHTTP = require('express-graphql');
const { schema, rootValue } = require('./GraphQLSchema');
const app = express();
app.use('/graphql', graphqlHTTP({
schema,
rootValue,
graphiql: true, /*** Enable GraphiQL ***/
}));
app.listen(3000);
Schema
Type
GraphQLScalarType
- String
- Int
- Float
- Boolean
- ID: Int or String
不允許空值
String!
: 代表不能為 null 的 String
List
[String!]!
: 不能為 null 的 List 裡面放不能是 null 的 String
ObjectType
可以用 Object 的方式來形成另一個 Type
type User {
name: String!
}
type Post {
id: ID!
author: User!
title: String!
body: String!
comments: [String!]!
}
R
UD: 讀取)
Query (CBasic
- 建立一個含有 Query 的 schema
exports.schema = buildSchema(`
type User {
id: ID!
name: String!
}
type Post {
id: ID!
title: String!
body: String!
}
/*******************/
type Query {
users: [User!]!
posts: [Post!]!
}
/*******************/
`);
- 定義 rootValue
exports.rootValue = {
users: () => Object.keys(usersById).map(id => usersById[id]),
posts: () => Object.keys(postsById).map(id => postsById[id]),
};
Relation
- 讓 schema 中的 type 彼此有關聯
type User {
id: ID!
name: String!
posts: [Post!]! /*** Posts ***/
}
type Post {
id: ID!
author: User! /*** User ***/
title: String!
body: String!
}
- 自行定義 resolve 的邏輯
class GraphQLUser {
constructor({ id, name }) {
this.id = id;
this.name = name;
}
posts() {
// ...待實作
}
}
class GraphQLPost {
constructor({ id, authorId, title, body }) {
this.id = id;
this.authorId = authorId;
this.title = title;
this.body = body;
}
author() {
// ...待實作
}
}
exports.rootValue = {
users: () => Object.keys(usersById).map(
id => new GraphQLUser(usersById[id])
),
posts: () => Object.keys(postsById).map(
id => new GraphQLPost(postsById[id])
),
};
參數
- GraphQL 是透過在 Key 後面放置
()
來傳遞參數
{
user(id: 1) {
name
}
}
- 定義把
id: ID!
傳遞給user
的 Resolving Function:
type Query {
users: [User!]!
posts: [Post!]!
user(id: ID!): User
}
exports.rootValue = {
// ...
user: ({ id }) => usersById[id] ? new GraphQLUser(usersById[id]) : null,
};
Summary
- Schema 定義
type Query {}
- rootValue 定義 resolving function
C
RUD
: 建立, 更新, 刪除)
Mutation (- Schema 定義 Mutation
type Mutation {
mutationName(arg: Type!): ResponseType
}
- 當有許多欄位時 Query 會平行的去跑,而 Mutation 則會一個一個執行
新增使用者
- 定義 Schema
type Mutation {
addUser(name: String!): User
}
- 實作 Resolving Function
let nextId = 2
const mutations = {
addUser: ({ name }) => {
const newUser = {
id: nextId,
name,
};
usersById[nextId] = newUser;
nextId++;
return new GraphQLUser(newUser);
},
}
- 將 Resolving Function 新增到
rootValue
exports.rootValue = {
...queries,
...mutations,
}
更改使用者名稱
三步驟
- Schema
type Mutation {
...
renameUser(id: Int!, name: String!): User
}
- Resolving Function
const mutation = {
...
renameUser: ({ id, name }) => {
usersById[id].name = name;
return new GraphQLUser(usersById[id]);
},
};
- rootValue
刪除使用者
...
Fragment
- 讓各模組自己定義各自的資料需求,並拼裝合成一個 Query 統一發送到伺服器,是以 GraphQL 為主的前端架構的關鍵。
- Example
{
users {
...userInfo
}
}
fragment userInfo on User {
id
name
}
- Goods
- 可以直接在對應的 Type 上重複查詢這組屬性
- 拼裝 Query
拼裝 Query
{
users {
...userInfoRequiredByComponent1
...userInfoRequiredByComponent2
...userInfoRequiredByComponent3
}
}
fragment userInfoRequiredByComponent1 on User {
id
}
fragment userInfoRequiredByComponent2 on User {
name
}
fragment userInfoRequiredByComponent3 on User {
id
name
}