什麼是 GraphQL? 為什麼要用 GraphQL? 它解決了什麼問題?

2025年7月14日

💎 加入 E+ 成長計畫 與超過 700+ 位工程師一同在社群成長,並獲得更多深度的軟體前後端學習資源

在開發應用程式時,處理使用者個人資料是個很常見的需求。假設某個需要在畫面上顯示使用者的姓名、大頭貼,以及最近的動態,相信對多數讀者來說,這聽起來很簡單。但如同寫任何程式,在實作時可能會遇到意料之外的問題。

針對這個需求,先讓我們看看用傳統 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 可以看出幾個重點:

  1. 每個 type 都定義了有哪些欄位可以拿
  2. 驚嘆號(!)代表這個欄位一定會有值,不會是空的
  3. 方括號([])代表這是一個陣列,裡面有多筆資料

另外要注意的是,這些資料類型之間是有關聯的。比如 Userposts,每個 Postauthor(又是一個 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+ 成長計畫,透過每週的深度主題文,以及技術討論社群,讓讀者在前端、後端、軟體工程的領域上持續成長。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們