GraphQL - arthur791004/notes GitHub Wiki

Why

REST 的問題

  • 參數、回傳值型別不固定
    • content-type: x-form-www-urlencoded, json, form-data
    • return format: number? string?
  • 多版本時,前後端常不匹配
  • 會拿多餘的欄位 (浪費頻寬)
    • 通常會回傳全部的資訊,但其實有些並不需要
    • 可加上 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!]!
}

Query (CRUD: 讀取)

Basic

  • 建立一個含有 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

  1. Schema 定義 type Query {}
  2. rootValue 定義 resolving function

Mutation (CRUD: 建立, 更新, 刪除)

  • Schema 定義 Mutation
type Mutation {
  mutationName(arg: Type!): ResponseType
}
  • 當有許多欄位時 Query 會平行的去跑,而 Mutation 則會一個一個執行

新增使用者

  1. 定義 Schema
type Mutation {
  addUser(name: String!): User
}
  1. 實作 Resolving Function
let nextId = 2

const mutations = {
  addUser: ({ name }) => {
    const newUser = {
      id: nextId,
      name,
    };
    usersById[nextId] = newUser;
    
    nextId++;
    
    return new GraphQLUser(newUser);
  },
}
  1. 將 Resolving Function 新增到 rootValue
exports.rootValue = {
  ...queries,
  ...mutations,
}

更改使用者名稱

三步驟

  1. Schema
type Mutation {
  ...
  renameUser(id: Int!, name: String!): User
}
  1. Resolving Function
const mutation = {
  ...
  renameUser: ({ id, name }) => {
    usersById[id].name = name;

    return new GraphQLUser(usersById[id]);
  },
};
  1. rootValue

刪除使用者

...

Fragment

  • 讓各模組自己定義各自的資料需求,並拼裝合成一個 Query 統一發送到伺服器,是以 GraphQL 為主的前端架構的關鍵。
  • Example
{
  users {
    ...userInfo
  }
}

fragment userInfo on User {
  id
  name
}
  • Goods
    1. 可以直接在對應的 Type 上重複查詢這組屬性
    2. 拼裝 Query

拼裝 Query

{
  users {
    ...userInfoRequiredByComponent1
    ...userInfoRequiredByComponent2
    ...userInfoRequiredByComponent3
  }
}

fragment userInfoRequiredByComponent1 on User {
  id
}

fragment userInfoRequiredByComponent2 on User {
  name
}

fragment userInfoRequiredByComponent3 on User {
  id
  name
}

Others: Interface、Union、Alias、Directive

Advanced

Authentication

Permission

Relation

Pagination

Cache

Error Handling

Reference