javascript graphql api

[JS] GraphQL 查詢語言介紹

Jeremy Jeremy
2026.03.21

這篇假設你已經知道基本的 API 串接概念(fetch / axios)與 RESTful API 是什麼。 如果還不熟,建議先看 [JS] API 串接

什麼是 GraphQL?

GraphQL 是 Facebook(現 Meta)在 2012 年內部開始使用、2015 年正式開源的一種 API 查詢語言,同時也是一套執行這些查詢的 runtime。

聽起來很抽象,直接跟 RESTful API 比比看。

假設今天我要做一個頁面,需要顯示某個使用者的名字、他發過的文章標題、以及每篇文章的留言數。 用 RESTful API 的話,可能要打三支 API:

GET /users/1
GET /users/1/posts
GET /posts/:id/comments  (每篇文章各一次)

這個問題有個名字叫做 over-fetching 與 under-fetching

  • over-fetching:每支 API 回來的資料裡,有一大堆我根本用不到的欄位,全都白白傳輸了。
  • under-fetching:一次請求拿到的資料不夠,必須再發第二、第三次請求。

GraphQL 的解法是:讓 client 自己決定要什麼資料。 不管多複雜的需求,只需要打一次請求,並且精確地描述你要的欄位結構:

query {
  user(id: 1) {
    name
    posts {
      title
      comments {
        count
      }
    }
  }
}

Server 就只會回傳你要求的那些欄位,不多也不少。

GraphQL 不是 RESTful API 的競爭對手,它是另一種設計 API 的思路。 對於資料結構複雜、前端有多種不同需求的場景(例如同一份資料要同時供 web 跟 mobile 使用,但需要的欄位不一樣),GraphQL 有明顯優勢。 但對於資料結構簡單的小型專案,RESTful 通常還是更直覺、更快速的選擇。

GraphQL 的核心概念

Schema 與 Type

GraphQL 的 server 端會定義一份 Schema,描述整個 API 有哪些資料、每種資料長什麼形狀(型別)。 可以把它想成是整個 API 的說明書,客戶端只能查 Schema 裡有定義的東西。

Schema 裡會定義 Type,例如:

type Character {
  id: ID!
  name: String!
  status: String
  species: String
  origin: Location
}

type Location {
  name: String
}

! 代表這個欄位不可為 null(必定有值)。 Type 可以互相嵌套,就像上面的 Character 裡面包了 Location

Query

Query 是用來讀取資料的,對應 RESTful 的 GET。 語法上先宣告 query,然後描述你想要的資料結構:

query {
  character(id: 1) {
    name
    status
    origin {
      name
    }
  }
}

Mutation

Mutation 是用來寫入或修改資料的,對應 RESTful 的 POST / PUT / DELETE。 語法跟 Query 很像,只是把關鍵字換成 mutation

mutation {
  createCharacter(name: "Jeremy", species: "Human") {
    id
    name
  }
}

Variables

Query 跟 Mutation 裡的動態值,不應該直接硬寫在查詢字串裡,而是透過 variables 傳入。 這樣查詢語法可以重複使用,安全性也更好(避免 injection 問題):

query GetCharacter($id: ID!) {
  character(id: $id) {
    name
    status
  }
}

然後把變數值另外帶過去:

{
  "id": "1"
}

Fragment

當多個 Query 需要取相同的欄位組合時,可以用 Fragment 把那組欄位抽出來重複使用,避免複製貼上。

例如角色的基本資訊可能在好幾個 Query 裡都會用到:

fragment CharacterBasic on Character {
  name
  status
  species
  origin {
    name
  }
}

定義好之後,在 Query 裡用 ...FragmentName 展開:

query GetCharacter($id: ID!) {
  character(id: $id) {
    ...CharacterBasic
  }
}

query GetCharacters {
  characters {
    results {
      ...CharacterBasic
    }
  }
}

Fragment 在前端框架裡特別實用:每個元件可以定義自己需要的欄位 Fragment,由父元件的 Query 把所有 Fragment 組合起來一次請求,欄位需求就跟著元件走,不需要集中管理。

範例 API 與前置作業

這篇使用 Rick and Morty API 作為練習對象。 這是一個完全公開、不需要任何 API key 的 GraphQL endpoint,非常適合拿來練手。

endpoint:https://rickandmortyapi.com/graphql

建議在動手寫 code 之前,先到 Rick and Morty GraphQL Playground 用瀏覽器試打幾個 query,感受一下 GraphQL 的查詢方式。

HTML 基本架構:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>GraphQL Demo</title>
  </head>
  <body>
    <input id="char-id" type="number" placeholder="輸入角色 ID(1~826)" value="1" />
    <button id="search-btn">查詢</button>
    <pre id="result"></pre>

    <script src="index.js"></script>
  </body>
</html>

用 fetch 打 GraphQL

這是最重要的一個認知:GraphQL 的底層就是一個普通的 HTTP POST 請求。 不管用什麼工具,最終都是往 endpoint 送一個 JSON,裡面帶著 query 字串跟 variables。 所以原生的 fetch 完全夠用:

const endpoint = 'https://rickandmortyapi.com/graphql'
const btn = document.getElementById('search-btn')
const input = document.getElementById('char-id')
const result = document.getElementById('result')

btn.addEventListener('click', fetchCharacter)

async function fetchCharacter() {
  const id = input.value

  const query = `
    query GetCharacter($id: ID!) {
      character(id: $id) {
        name
        status
        species
        origin {
          name
        }
      }
    }
  `

  try {
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        query,
        variables: { id },
      }),
    })

    const { data, errors } = await response.json()

    if (errors) {
      result.textContent = JSON.stringify(errors, null, 2)
      return
    }

    result.textContent = JSON.stringify(data.character, null, 2)
  } catch (error) {
    console.error(error)
  }
}

注意兩個 GraphQL 特有的地方:

  1. 永遠是 POST,不管是查詢還是寫入。
  2. HTTP status 幾乎永遠是 200,GraphQL 把錯誤資訊包在 response body 的 errors 欄位裡回傳,而不是用 HTTP 狀態碼表示失敗。所以記得要檢查 errors,光看 response.ok 是不夠的。

GraphQL 的這個設計跟 RESTful 很不一樣,很多人第一次踩坑就是在這裡——看到 200 OK 以為成功了,結果 data 是 null,錯誤全在 errors 裡面。

延伸:Apollo Client

GraphQL 生態系裡功能最完整的 client 框架是 Apollo Client,提供自動快取、與 React / Vue 深度整合的宣告式查詢、以及 WebSocket Subscription 支援。但 bundle size 相對大,小型專案通常只需要 fetch 就夠用。