什麼是 GraphQL? 為什麼要用 GraphQL? 它解決了什麼問題?
2025年7月14日
在開發應用程式時,處理使用者個人資料是個很常見的需求。假設某個需要在畫面上顯示使用者的姓名、大頭貼,以及最近的動態,相信對多數讀者來說,這聽起來很簡單。但如同寫任何程式,在實作時可能會遇到意料之外的問題。
針對這個需求,先讓我們看看用傳統 RESTful API 的做法要如何實作。一般來說,我們會先呼叫 GET /users/123
來取得使用者資料:
{
"id": 123,
"name": "陳小美",
"email": "[email protected]",
"avatar": "profile.jpg",
"phone": "02-2345-6789",
"address": "台北市大安區敦化南路一段123號",
"birthdate": "1990-05-15",
"preferences": { "theme": "dark", "notifications": true },
"last_login": "2023-01-01T10:30:00Z",
"created_at": "2020-01-01T00:00:00Z",
"updated_at": "2023-01-01T00:00:00Z"
}
透過呼叫這支 API,我們能拿到使用者資料。但問題來了「我們真的需要這麼多資訊嗎?」
假如在呼叫這支 API 的頁面,我們只需要姓名和大頭照,其他的資訊 (包含電話號碼、地址、偏好設定) 都用不到,這時如果 API 也回傳,對客戶端來說就顯得不必要,浪費網路的頻寬,這是典型 over-fetching(拿太多) 的問題。
然而,除了這點,還有另一個常見的問題。如果我們要顯示使用者最近的貼文,就需要再呼叫一次 GET /users/123/posts
。想要顯示每篇貼文的留言?又要呼叫更多支 API。這會導致的結果是,一個簡單的個人資料畫面可能需要對伺服器發出好幾個請求。這是典型的 under-fetching(拿不夠) 的問題,意即每次請求都無法獲得完整所需的資料。
上面描述的問題,對應用程式來說很不理想,特別是在行動網路環境下,因為網路速度不夠快,拿太多資料會導致載入時間變長,流量消耗增加,使用者可能會因為等太久而離開。
相信讀者們這時會問「有沒有方法能解決這類問題呢?」有的,事實上 GraphQL 就是為了解決這些問題而生的。以下讓我們來具體談談 GraphQL 是什麼、能帶來什麼好處 (備註:要了解 GraphQL 前,推薦對傳統 RESTful API 不熟的讀者可以先了解一下,這樣更容易理解 GraphQL 帶來什麼價值)。
透過 GraphQL 精準取得所需的資料。
在實際談 GraphQL 是什麼之前,讓我們先換個角度思考思考拿資料這個問題。以大家平常到餐廳點菜的經驗來說,當到了餐廳時,有些餐廳可能只提供套餐,這會帶來些困擾,因為假如某個套餐中,有你不想要的餐點,點套餐可能會很浪費。反之,假如能夠單點的話,就可以很精準地說「我要鮭魚、蛋炒飯,加一個刈包不加香菜」,用這種方式,能避免點的東西有自己不想要的。
但在傳統 RESTful API 的世界裡,我們會被困在固定套餐的模式中。以上面拿資料的例子來說,用 RESTful 的方式,就像在點「使用者套餐」或「貼文套餐」,使用者套餐每次拿到的,就是一整包,即使有些東西客戶端不需要,還是會一併拿到。
反過來,如果 API 的運作方式也能改成精確描述的形式,上面這種套餐式的拿法,就可以改成下面這樣。
query GetUserProfile($userId: ID!) {
user(id: $userId) {
name
avatar
posts(limit: 5) {
title
createdAt
comments(limit: 3) {
content
author {
name
}
}
}
}
}
上面這種拿資料的方式,請求的結構與客戶端想要回傳的結構完全相符。而這就是 GraphQL 能帶來的幫助,讓客戶端可以依照需求,來拿當下頁面所需的東西,不多也不少。
讀完上面的內容,相信讀者們可能會覺得 GraphQL 聽起來很不錯,但實際上是如何運作的? 以下讓我們一起一探究竟。
GraphQL 的運作機制
看到上面的例子,相信讀者們會好奇 GraphQL 實際上是如何運作的。讓我們延續餐廳的比喻來說明。
還記得前面提到的,傳統 RESTful API 就像套餐式的點餐方式,如果要進一步比喻,RESTful API 就像受限於某些特定的套餐,想吃不同的菜,只能多點不同的套餐,然後拿自己想吃的部分;而 GraphQL 就像是能夠自由點菜的餐廳,只要跟一個服務生說「我要鮭魚、蛋炒飯,加一個刈包不加香菜」,這個服務生就會幫你統一處理。
回到 API 來談,傳統 RESTful API 需要多個接口 (/users
、/posts
、/comments
),就像點不同的套餐,裡面可能會有自己不要的內容;但 GraphQL 只需要一個接口 (通常是 /graphql
),想點什麼可以自由搭配。
當你跟 GraphQL 餐廳的服務生點餐時,會經過以下幾個步驟:
第一步:檢查菜單
你告訴服務生想要什麼餐點後,服務生會先檢查菜單,確認你點的東西餐廳有沒有提供。這邊就會用到 schema 的概念。schema 就像餐廳的菜單,定義了有哪些資料可以拿、怎麼拿。如果你點了菜單上沒有的東西,服務生就會直接告訴你「抱歉,我們沒有這道菜」;如果你拿了某個 schema 中沒有的值,GraphQL 也會回傳沒有該欄位的相關錯誤。
第二步:分工處理
確認餐點都在菜單上後,服務生會把你的訂單拆解,分別交給廚房的不同部門處理。每個部門就像 GraphQL 當中 resolver。比如說,鮭魚交給主廚處理、蛋炒飯交給炒鍋師傅、刈包交給點心部門,每個部門都專精處理特定類型的餐點。在 GraphQL 中,則會跟不同的資料源拿資料。
第三步:同時製作
就像餐廳中不同的部門可以同時開工,主廚在處理鮭魚的同時,炒鍋師傅也在炒飯,點心部門也在準備刈包。不用等一道菜做完才做下一道,大幅縮短等餐時間。在 GraphQL 中,假如同時要拿使用者、動態、留言的資料,這三者也能同時進行。
第四步:統一出餐
最後,所有餐點都做好後,服務生會按照你原本要求的方式擺盤出餐。完全符合你點的內容,不會多也不會少。這就是 GraphQL 特別好用的地方,不像傳統 REST API 只能回傳固定格式的資料,GraphQL 讓客戶端可以精準描述自己要什麼,伺服器就回傳什麼。
動手寫第一個 GraphQL 查詢
了解 GraphQL 的基本概念後,接下來我們實際動手寫個查詢看看。沒寫過也別擔心,我們會一步步帶大家操作。
要先知道有哪些資料可以拿
在開始寫查詢之前,我們得先了解這個 API 能提供哪些資料。這時會用到上面有提及的 schema。schema 就像一張資料菜單,告訴我們有哪些資料類型、每種類型有什麼欄位、以及它們彼此間的關係。
具體來說,這個例子的 schema 可能會長這樣:
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
}
type Comment {
id: ID!
content: String!
author: User!
}
從這個 schema 可以看出幾個重點:
- 每個 type 都定義了有哪些欄位可以拿
- 驚嘆號(
!
)代表這個欄位一定會有值,不會是空的 - 方括號(
[]
)代表這是一個陣列,裡面有多筆資料
另外要注意的是,這些資料類型之間是有關聯的。比如 User
有 posts
,每個 Post
有 author
(又是一個 User
),還有 comments
。這種關聯性讓我們可以在一個查詢中,沿著這些關係一路拿到相關的資料。
GraphQL 查詢實際長什麼樣?
假設我們要做一個電商網站的商品頁面,需要顯示商品名稱、價格,還有最近幾則評價。用 GraphQL 的話會怎麼寫呢?
先想想看,我們要拿的資料包括商品本身的資訊,還有相關的評價資料。按照 GraphQL 的邏輯,查詢應該會長這樣:
query GetProductDetails($productId: ID!) {
product(id: $productId) {
name
price
description
reviews(limit: 5) {
rating
comment
reviewer {
name
}
}
}
}
這個查詢可以讓人一眼看出在講「透過這個產品的 ID,給我它的名稱、價格、描述,以及前 5 個評論,包括每個評論者的姓名」。這種易讀的形式,也是 GraphQL 的好用的地方之一。
在了解完讀取資料後,讀者可能會問「如果需要建立或更新東西時該怎麼處理?」在 GraphQL 中,我們會用 mutations 來做這件事。它的運作方式類似於查詢,但會用在修改資料而不是讀取。
mutation CreatePost($input: PostInput!) {
createPost(input: $input) {
id
title
content
author {
name
}
}
}
重新檢視 RESTful API 與 GraphQL 的區別
為了真正理解 GraphQL 的價值,讓我們透過一個實際例子來看。想像你正在建立一個社群媒體管理後台,需要顯示使用者資訊、他們最近的貼文和粉絲數。
使用 REST API 可能需要進行多個請求:
// 首先,取得使用者
const userResponse = await fetch("/api/users/123");
const user = await userResponse.json();
// 然後取得他們的貼文
const postsResponse = await fetch("/api/users/123/posts?limit=5");
const posts = await postsResponse.json();
// 最後,取得粉絲數
const followersResponse = await fetch("/api/users/123/followers/count");
const followerCount = await followersResponse.json();
如同前面提到的問題,每個接口都可能有客戶端可能用不到的資料,這種浪費相當不必要。
如果改成用 GraphQL,則能夠精準描述
query Dashboard($userId: ID!) {
user(id: $userId) {
name
avatar
posts(limit: 5) {
title
createdAt
likesCount
}
followersCount
}
}
從上面兩種拿資料的方式可以看到,GraphQL 只需一個請求而不是三個。資料傳輸量減少,能更快拿完所需的資料,讓載入資料變得更快。
GraphQL 適用什麼情境?
雖然前面談了 GraphQL 的好處,但它也不是萬靈丹,只有在某些場景下會比較適用。
舉例來說,假如傳輸資料時需要斤斤計較每一 KB 的狀況 (例如在客戶端只有 3G 網路的情境下),這時載入太慢使用者可能等不了就關掉應用程式。在這種情況下,GraphQL 最小化資料傳輸和減少往返次數的特性就特別有幫助。
此外,當處理的資料複雜、相互關聯,例如一個電商平台,產品有變體、類別、評論,且彼此相關。用 RESTful API 需要為每個關係進行單獨的 API 呼叫。但如果用 GraphQL,就可以在單一查詢中遍歷這些關係。
最後,還有一個特別適合用 GraphQL 的場景,那就是有多種不同的客戶端;例如有網頁版、行動版和桌機版,每個都需要來自相同底層的資料,但具體要拿的可能有些微差異。這種狀況下用 RESTful API,就要為每個不同的客戶端,開發不同的 API 接口,例如行動端用 /api/mobile/user-profile
,網頁端 /api/web/user-profile
,讓維護的成本變得更高。
但如果用 GraphQL,同一個接口可以讓每個客戶端,選擇自己要請求的資料;行動端可以只要求基本欄位,網頁端可以請求更詳細的資訊。
重要的是想解決的問題
雖然前面談到了 GraphQL 的好處,但這不代表 GraphQL 在任何地方都比 RESTful API 或其他類型的 API 來得好。
是否選擇用 GraphQL,或是選用其他類型的 API 形式,端看想要解決的問題、所面對的情境。舉例來說,如果只是開發一個簡易的應用程式,沒有過於複雜的客戶端與資料請求,用 RESTful API 開發起來會更簡單快速,且不會有上面提到的拿太多 (over-fetching) 以及拿不夠 (under-fetching) 的問題,這種狀況下就更推薦用 RESTful API。
當然,假如你還沒辦法很明確做出不同 API 形式間的技術選擇,最推薦的方式是實際去試試看不同的類型,多實作後就會更有感覺,也能讓自己在不同場景下,更有效選擇合適的技術。
加入 E+ 成長計畫
如果你覺得這篇內容有幫助,喜歡我們的內容,歡迎加入 ExplainThis 籌辦的 E+ 成長計畫,透過每週的深度主題文,以及技術討論社群,讓讀者在前端、後端、軟體工程的領域上持續成長。