# GraphQL 101 - Graph Query Language ## 什麼是 GraphQL GraphQL 是一種 API 查詢語言,能讓客戶端更靈活地取得後端資料。 最初由 Facebook 開發,解決手機 App 初次載入時發送多個資料請求,導致速度變慢的問題。為解決此問題,Facebook 設計了 GraphQL,讓 server side 能夠一次性提供所有所需資料,減少請求次數,提升效能。 2015 年,Facebook 對外發佈了這項技術。 ## GraphQL 與 RESTful 比較 1. API 端點 - `RESTful` 通常有多個端點: - `/users/<id>` - `/users/<id>/posts` - `/users/<id>/followers` - `GraphQL` 僅有單一端點: - `/graphql` 2. 資料擷取 - 情境:想瞭解 Mary 的文章有多少 followers - `RESTful` - 需要依賴 2 支 API 才能取得所需資料,因為 followers API (B) 的資料依賴於 posts API (A)。 ![](https://i.imgur.com/Wg2RlhE.png) - `GraphQL` - 可將不同類型的資料集中在同一筆 request 內。資料間有關聯,如 A 的 response 含 B 所需的 params,B 可直接嵌入 A 的 query 裡,僅需單一請求即可。 ![](https://i.imgur.com/aXeOu3I.png) (圖片來源:[HowToGraphQL](https://www.howtographql.com/basics/1-graphql-is-the-better-rest/)) 3. Schema & Type System - REST 資料為弱式類型。因此,client side 必須在傳回資料時,決定如何解譯格式化的資料。 - GraphQL 資料為強式類型。因此,client side 以預先決定且相互了解的格式接收資料。 | | REST | GraphQL | | -------- | -------- | -------- | | 定義 | REST 用於定義用戶端與伺服器間結構化資料交換的規則。 | GraphQL 是一種查詢語言、架構樣式和用於建立和操作 API 的工具集。 | | 適用情境 | REST 適用於明確定義資源的簡單資料來源。 | GraphQL 適用於大型、複雜且相互關聯的資料來源。 | | 資料取得 | REST 以 URL 形式的多個端點定義資源。 | GraphQL 以單一的 URL 端點。 | | 資料回傳 | REST 以伺服器定義的固定結構傳回資料。 | GraphQL 以用戶端定義的彈性結構傳回資料。 | | 資料結構與定義 | REST 資料弱式類型,需由用戶端在回傳資料時,決定如何解譯格式化的資料。 | GraphQL 資料強式類型,用戶端以預先決定且相互了解的格式接收資料。 | | 錯誤檢查 | REST 使用時,用戶端需檢查回傳資料是否有效。 | 使用 GraphQL 時,schema 結構會拒絕無效請求,並自動產生錯誤訊息。 | (資料來源:[AWS官方對比GraphQL與REST](https://aws.amazon.com/tw/compare/the-difference-between-graphql-and-rest/)) ## GraphQL 解決的問題 ### 1. 固定結構資料交換 REST API 要求用戶端請求遵循固定結構,這種剛性結構易於使用,但並非總是交換所需資料的最有效方法。GraphQL 則讓 client side 以其定義的彈性結構傳回資料。 ### 2. 過多或過少的資料取得 (Overfetching and Underfetching) - `RESTful` 容易含過多或缺少一些資料,因為 endpoint 回的 response 資料是固定的。 - `GraphQL` 可精準挑選所需要的資料。 ## 如何使用 ### GraphQL Server 1. 建立 GraphQL server:這裡使用 Node.js 的 express-graphql 框架 2. 定義 Schema 來描述數據模型和操作。 3. 定義 Resolver,Resolver是一個函數,用於處理 query 和 mutation 請求,並返回相應的數據。 4. 設置GraphQL端點:在搭建好GraphQL服務器後,您需要設置一個GraphQL端點,用於接收和處理客戶端的查詢和變更請求。 5. 發送查詢和變更請求:http://localhost:4000/graphql 有 GraphiQL 可向 GraphQL 端點發送 query 和 mutation 請求。 ``` npm install express express-graphql graphql --save ``` ```javascript var express = require("express"); var { graphqlHTTP } = require("express-graphql"); var { buildSchema } = require("graphql"); // Sample data (to simulate a database) let users = [ { id: "1", name: "Alice", age: 25 }, { id: "2", name: "Bob", age: 30 }, ]; console.log(users); // Construct a schema, using GraphQL schema language // ! 表示 non-nullable const schema = buildSchema(` type User { id: ID! name: String! age: Int! } type Query { hello: String users: [User!]! user(id: ID!): User } type Mutation { createUser(name: String!, age: Int!): User! } `); // The root provides a resolver function for each API endpoint const root = { // query hello: () => "Hello world!", users: () => users, user: ({ id }) => { const foundUser = users.find((user) => user.id === id); return foundUser ? foundUser : null; // Handling user not found }, // mutation createUser: ({ name, age }) => { const newId = (users.length + 1).toString(); // Safer way to generate ID const newUser = { id: newId, name, age }; users.push(newUser); return newUser; }, }; var app = express(); app.use( "/graphql", graphqlHTTP({ schema: schema, rootValue: root, graphiql: true, }) ); app.listen(4000); console.log("Running a GraphQL API server at http://localhost:4000/graphql"); ``` ### GraphQL Client GraphQL Client 主要工作就是,使用前端寫好的 query 與 server 溝通,即 client 只需要寫 query,打 API 行為就交給工具函式庫幫你解決,相當於在使用 RESTful API 時候,會去用 axios 幫助處理 API。 常見工具: - [Apollo Client](https://www.apollographql.com/docs/) - [graphql-request](https://github.com/prisma-labs/graphql-request) - [relay](https://relay.dev/) **Code Practice** ``` npm install graphql-request ``` ```javascript const { request, gql } = require("graphql-request"); const endpoint = "http://localhost:4000/graphql"; // Your GraphQL server URL // Sample query const getUsersQuery = gql` query { users { id name age } } `; // Sample mutation const createUserMutation = gql` mutation ($name: String!, $age: Int!) { createUser(name: $name, age: $age) { id name age } } `; // Function to fetch users async function getUsers() { try { const data = await request(endpoint, getUsersQuery); console.log("Users:", data.users); } catch (error) { console.error("Error fetching users:", error); } } // Function to create a user async function createUser(name, age) { try { const data = await request(endpoint, createUserMutation, { name, age }); console.log("Created user:", data.createUser); } catch (error) { console.error("Error creating user:", error); } } // // Uncomment and use this to create a user // createUser("David", 35); // Usage getUsers(); // Fetch users ``` ### Query and Mutation #### Query - `基本` ```graphql query { hello users { id name age } } ``` 得到 server 的 response: ```json { "data": { "hello": "Hello world!", "users": [ { "id": "1", "name": "Alice", "age": 25 }, { "id": "2", "name": "Bob", "age": 30 } ] } } ``` - `帶參數` ```graphql query { user(id: "1") { name age } } ``` 得到的 response: ```json { "data": { "user": { "name": "Alice", "age": 25 } } } ``` #### Mutation ```graphql mutation { createUser(name: "Charlie", age: 28) { id name age } } ``` API 成功後,回傳的 response: ```json { "data": { "createUser": { "id": "3", "name": "Charlie", "age": 28 } } } ``` ## 使用情境 在以下情況,GraphQL 可能是更好的選擇: - 頻寬有限,並且想要減少請求和回應的數量。 - 有多個資料來源,希望將它們結合在一個端點。 - client side 請求顯著變化,並預期非常不同的回應。 ![image](https://hackmd.io/_uploads/Sk_yQUsQa.png) ## 參考來源 - [GraphQL官方網站](https://graphql.org/) - [GraphQL官方網站](https://graphql.org/graphql-js/) - [HowToGraphQL](https://www.howtographql.com/) - [Solo.io GraphQL 相關主題](https://www.solo.io/topics/graphql/) - [AWS官方對比GraphQL與REST](https://aws.amazon.com/tw/compare/the-difference-between-graphql-and-rest/) - [AlphaCamp文章](https://tw.alphacamp.co/blog/graphql)