GraphQL 詳細繁體中文教學網站
GraphQL 是一種 API 查詢語言與執行模型,特色是讓前端或客戶端可以「精準請求需要的資料」,避免 REST 常見的 over-fetching 與 under-fetching 問題。它的查詢能力建立在強型別 Schema 之上,並透過 Query、Mutation 等操作來讀寫資料。
學習重點一覽
1. 認識 GraphQL
GraphQL 的核心精神是:由客戶端描述自己要什麼資料,而不是由伺服器固定回傳整包資料。查詢可以只取所需欄位,也能一次走訪關聯物件,減少多次 API 請求。
為什麼很多人喜歡 GraphQL?
你只請求真正需要的欄位,例如只要使用者的 name 與 email,就不必額外拿到大量不用的資料。
GraphQL 可以沿著物件關係一路往下查,例如查詢使用者時,同時拿到其文章與文章留言。
API 能提供清楚的資料結構定義,方便前後端協作,也利於工具自動補完與文件生成。
GraphQL 與 REST 的直觀差異
| 比較面向 | REST | GraphQL |
|---|---|---|
| 資料取得方式 | 依 endpoint 回傳固定格式 | 由客戶端宣告要哪些欄位 |
| 關聯資料 | 常需多次請求 | 可在單一查詢中巢狀取得 |
| 文件與型別 | 常靠額外文件維護 | Schema 就是契約與型別定義 |
| 資料寫入 | POST / PUT / PATCH / DELETE | 透過 Mutation 執行 |
2. Schema 與型別系統
GraphQL 的 Schema 是整個 API 的契約。它描述有哪些型別、欄位、參數與回傳值。 型別系統包含 Scalar、Object、Argument、List、Non-Null、Enum、Interface、Union 與 Input Object 等核心概念。
常見型別概念
| 型別 | 說明 |
|---|---|
| Scalar | 純量值,例如 Int、Float、String、Boolean、ID |
| Object | 代表可查詢的物件,如 User、Post |
| Argument | 欄位的參數,GraphQL 採具名參數 |
| List | 以 [Type] 表示陣列型別 |
| Non-Null | 以 ! 表示不可為 null,例如 String! |
| Enum | 限制欄位值只能在固定集合中選擇 |
| Interface | 抽象型別,定義實作者都必須具備的欄位 |
| Union | 多個具體型別的聯集,不保證共享欄位 |
| Input Object | 用於 Mutation 等輸入資料結構 |
Schema SDL 範例
以下是使用 SDL(Schema Definition Language)撰寫的簡化範例:
type User {
id: ID!
name: String!
age: Int
role: UserRole!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String
published: Boolean!
author: User!
}
enum UserRole {
ADMIN
EDITOR
MEMBER
}
input CreatePostInput {
title: String!
content: String
authorId: ID!
}
type Query {
user(id: ID!): User
posts(keyword: String): [Post!]!
}
type Mutation {
createPost(input: CreatePostInput!): Post!
}
[Post!]! 的意思是:這是一個「不可為 null 的陣列」,而且陣列中的每個 Post 元素也不可為 null。
3. Query 查詢語法教學
Queries 教學重點包含:欄位(Fields)、參數(Arguments)、別名(Aliases)、片段(Fragments)、變數(Variables)、指令(Directives)與巢狀查詢。
3.1 最基本查詢:欄位選取
GraphQL 的核心就是「請求物件上的特定欄位」。如果欄位回傳的是物件型別,就能繼續往下巢狀選取子欄位。
query {
user(id: "101") {
id
name
age
}
}
3.2 傳入參數 Arguments
欄位可以接受參數,讓你在單一查詢中帶入條件,例如查特定 ID、關鍵字或分頁資訊。
query {
posts(keyword: "GraphQL") {
id
title
published
}
}
3.3 巢狀查詢
當欄位是物件或物件列表時,可以一路往下展開需要的資料,這是 GraphQL 很重要的能力。
query {
user(id: "101") {
name
posts {
title
published
author {
id
name
}
}
}
}
3.4 別名 Aliases
如果同一個欄位要查兩次、但參數不同,就需要使用別名,避免回傳結果欄位名稱衝突。
query {
adminUser: user(id: "1") {
id
name
}
normalUser: user(id: "2") {
id
name
}
}
3.5 片段 Fragments
片段可以把常用欄位集合抽出重用,減少重複撰寫與維護成本。
query {
user(id: "101") {
...UserBase
posts {
id
title
}
}
}
fragment UserBase on User {
id
name
role
}
3.6 變數 Variables
正式開發時,不建議手動把動態值直接串進查詢字串,而是把它們抽成變數。
query GetUser($userId: ID!) {
user(id: $userId) {
id
name
age
}
}
{
"userId": "101"
}
3.7 指令 Directives
GraphQL 內建常見執行指令如 @include 與 @skip,可依條件動態決定是否包含某欄位。
query GetUser($withAge: Boolean!) {
user(id: "101") {
id
name
age @include(if: $withAge)
}
}
3.8 初學者最常犯的錯誤
- 回傳物件型別時,忘記再展開子欄位。
- 同欄位多次查詢卻未使用別名。
- 直接把動態值寫進查詢字串,而不是使用變數。
- 沒有看 Schema 就硬寫查詢,導致欄位名稱錯誤。
4. Mutation 資料寫入教學
GraphQL 不只拿資料,也可以修改資料。新增、更新、刪除都通常透過 Mutation 完成。 頂層 mutation 欄位是序列執行,而不是像一般 query 欄位常見的平行解析。
4.1 新增資料
新增資料時,通常會搭配 Input Object 傳遞一組結構化輸入,然後回傳新建立的物件內容。
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
published
author {
id
name
}
}
}
{
"input": {
"title": "GraphQL 入門筆記",
"content": "這是一篇 GraphQL 教學文章",
"authorId": "101"
}
}
4.2 更新資料
Mutation 最好設計成「用途明確」的欄位,而不是所有更新都擠在一個過度通用的欄位裡。
mutation {
updateUserName(id: "101", name: "小美") {
id
name
}
}
updateUser 接很多可選欄位,像 updateUserName 這種目的明確的設計,常更容易驗證與維護。
4.3 刪除資料
刪除資料時,常見回傳值會是被刪除的 ID,或一個 payload 物件,用來表示刪除成功。
mutation {
deletePost(id: "501") {
id
success
message
}
}
4.4 Mutation 的重要觀念
- 只有頂層 mutation 欄位允許造成副作用。
- Mutation 可以包含多個欄位,但頂層欄位是依序執行。
- 序列執行不等於資料庫交易機制,部分欄位成功、部分欄位失敗仍可能發生。
5. 範例應用與實戰情境
下面用三個常見情境示範 GraphQL 的實務應用:電商平台、社群貼文系統與圖書管理系統。
範例一:電商平台
前端商品頁常需要同時拿到商品資訊、價格、庫存、評價與賣家資訊。若用 GraphQL,就能在單一請求中完整取得。
query ProductDetail($id: ID!) {
product(id: $id) {
id
name
price
stock
description
seller {
id
shopName
rating
}
reviews {
id
score
comment
user {
id
name
}
}
}
}
適用頁面:商品詳情頁、購物車預覽頁、商品推薦區塊。
範例二:社群貼文系統
社群動態牆通常會顯示貼文作者、文字、圖片、按讚數與留言。GraphQL 很適合一次把整個畫面所需資料拿齊。
query Feed {
posts {
id
content
likeCount
createdAt
author {
id
name
avatar
}
comments {
id
content
author {
id
name
}
}
}
}
mutation LikePost($postId: ID!) {
likePost(postId: $postId) {
id
likeCount
}
}
範例三:圖書管理系統
管理系統中可同時顯示書籍資料、作者資訊與借閱狀態,並能透過 Mutation 更新借閱紀錄。
query LibraryBooks {
books {
id
title
category
available
author {
id
name
}
currentBorrower {
id
name
}
}
}
mutation BorrowBook($bookId: ID!, $userId: ID!) {
borrowBook(bookId: $bookId, userId: $userId) {
id
available
currentBorrower {
id
name
}
}
}
前端 JavaScript 呼叫 GraphQL 範例
最簡單的方式是對 GraphQL endpoint 發送 POST,body 中包含 query 與 variables。
async function getUser(userId) {
const query = `
query GetUser($userId: ID!) {
user(id: $userId) {
id
name
age
}
}
`;
const variables = { userId };
const res = await fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ query, variables })
});
const result = await res.json();
return result.data.user;
}
初學者最佳實務建議
- 先設計清楚 Schema,再開始寫 resolver 與前端查詢。
- 善用 Input Object 讓 Mutation 輸入更有結構。
- 避免設計過度萬用的 Mutation,優先考慮用途明確的欄位。
- 在大型列表查詢中加入分頁設計。
- 從一開始就考慮授權、效能與安全限制。
- 將業務邏輯與授權邏輯維持在清晰的分層中,不要全部塞進解析器。
6. GraphQL 小測驗
做做看以下題目,檢查你是否已經掌握基本概念。按下送出後會立即顯示分數與評語。