探索 .graphql 檔案:前端與後端如何分工協作?

更新日期: 2025 年 5 月 8 日

當你第一次接觸 GraphQL,可能會感到困惑:

「我在專案中看到很多 .graphql 檔案,有些在後端、有些在前端,到底誰該寫什麼?這些檔案是在做什麼用?」

事實上,.graphql 是一種純文字格式的檔案,用來撰寫 GraphQL 的語法內容,但前後端使用 .graphql 的目的與內容是完全不同的

本篇文章將帶你從 .graphql 的角度,釐清以下三件事:

  1. .graphql 到底可以寫哪些東西?
  2. 前端與後端各自用 .graphql 做什麼?
  3. 如何從這個分工建立完整的 GraphQL 開發流程?

.graphql 是什麼?它能包含哪些內容?

在 GraphQL 專案中,你經常會看到副檔名為 .graphql 的檔案。

這些檔案表面上看起來都是純文字,但其實扮演著兩種截然不同的角色,端看你是從後端還是前端的角度來使用。

.graphql 的本質是什麼?

.graphql 是一種語言檔案格式,內容是用 GraphQL 專屬的語法(GraphQL SDL:Schema Definition Language)所撰寫的。

這些檔案的用途依照場景可分為兩大類:

類型一:Schema Definitions(Schema 定義語法)→ 後端使用

這類 .graphql 檔案的目的,是定義整個 API 的資料結構與行為,屬於 GraphQL 的「型別系統」。

你可以想像它是 API 的「說明書」,負責定義:

  • 有哪些資料類型(如 User、Post、Product)
  • 每個類型有哪些欄位、屬性是什麼資料型別
  • 哪些欄位可以為 null,哪些是必填
  • 查詢與操作的進入點(Query / Mutation / Subscription)

🔧 範例:

# schema.graphql
type User {
  id: ID!
  name: String!
  email: String
}

type Query {
  user(id: ID!): User
}

type Mutation {
  updateUser(id: ID!, name: String!): User
}

這些定義通常由後端工程師撰寫,會被 GraphQL Server(如 Apollo Server 或 Hasura)讀取並轉成實際可執行的 API。

這也是為什麼我們說這是後端的 GraphQL 檔案

類型二:Operation Definitions(操作語法)→ 前端使用

另一種 .graphql 檔案則是由前端工程師撰寫,它不定義資料結構,而是定義前端要向後端請求什麼資料

這類語法被稱為「操作語法」,包含三種主要類型:

操作類型說明
query查詢資料(最常見)
mutation修改資料(新增、更新、刪除)
subscription即時監聽資料變化(如聊天室、通知等場景)

範例:

# getUser.graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

mutation UpdateUser($id: ID!, $name: String!) {
  updateUser(id: $id, name: $name) {
    id
    name
  }
}

這些查詢語法會搭配像 Apollo Client 或 Relay 的 GraphQL 客戶端工具來發送請求。

後端的 .graphql:定義 API 規格的藍圖

在後端,.graphql 檔案的主要任務是定義 GraphQL Schema,這是整個 API 系統的根本。

也就是「你能查什麼資料」、「可以對哪些資料做哪些操作」,全部都寫在這裡。

你可以把它想像成一份 API 的契約文件(Contract),由後端定義、前端遵守。

這份契約不僅描述了資料結構,也定義了資料的操作方式,並且對整個系統提供強型別的保障。

型別與操作入口的定義

.graphql Schema 檔中,最常見的語法包括:

✅ 定義資料模型(Types)

type User {
  id: ID!
  name: String!
  email: String
}

這代表你的系統中有一種「使用者」資料,每個使用者有三個欄位:id、name 和 email,其中 id 與 name 是必填。

✅ 定義查詢進入點(Query)

type Query {
  user(id: ID!): User
  users: [User!]!
}

這代表前端可以透過 user(id: ...)users 這些查詢操作,來取得資料。每個欄位都像是 API 的一個 endpoint。

✅ 定義變更操作(Mutation)

type Mutation {
  updateUser(id: ID!, name: String!): User
  deleteUser(id: ID!): Boolean
}

這些定義表示前端可以呼叫 updateUserdeleteUser 來改變資料,類似 REST 中的 POST / PUT / DELETE 行為。

✅ 定義即時通訊(Subscription)

type Subscription {
  userAdded: User
}

如果你的應用需要即時功能(如聊天室、通知),可以使用 subscription 提供 WebSocket 資料流。

Schema 如何運作?

Schema 只是「宣告」,實際的資料來源與邏輯處理,會交給後端對應的 Resolver 函式 處理。例如:

const resolvers = {
  Query: {
    user: (_, { id }) => db.user.findById(id),
  },
  Mutation: {
    updateUser: (_, { id, name }) => db.user.update(id, name),
  },
}

這些 Resolver 就像是後端 API 的實作細節,Schema 則是對外公布的規則與使用說明。

前端的 .graphql:撰寫實際操作 API 的請求語法

前端開發者也會撰寫 .graphql 檔案,但用途與後端完全不同

前端寫的 .graphql 檔案不是用來定義 API,而是用來描述「我想向 API 請求什麼資料」。

這些操作定義會被 GraphQL Client(如 Apollo Client)解析,發送 HTTP 或 WebSocket 請求給後端的 GraphQL Server。

查詢資料:query

最基本的 GraphQL 查詢請求,功能類似 REST 的 GET:

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

修改資料:mutation

用來新增、修改或刪除資料,功能類似 POST / PUT / DELETE:

mutation UpdateUser($id: ID!, $name: String!) {
  updateUser(id: $id, name: $name) {
    id
    name
  }
}

訂閱資料:subscription

用來建立持久的即時連線,常見於聊天室、即時通知、遊戲狀態同步:

subscription OnUserAdded {
  userAdded {
    id
    name
  }
}

與 Codegen 工具結合:讓 .graphql 檔案成為型別安全的開發利器

前端開發者撰寫的 .graphql 檔案,原本只是純文字的查詢描述,用來向後端發送 API 請求。

但這樣的查詢如果每次都要手動組裝、串接、解析回傳資料,就會:

  • 重複撰寫相同的資料結構
  • 無法享受 TypeScript 的型別檢查
  • 增加錯誤與維護風險

這時候就可以搭配 GraphQL Code Generator(簡稱 Codegen)來解決這些問題。

Codegen 是什麼?

Codegen 是一套工具,可以根據:

  1. 後端提供的 GraphQL Schema
  2. 前端撰寫的 .graphql 查詢檔案

自動產生:

  • TypeScript 型別定義
  • React 專用的查詢 Hook 函式(也支援 Vue、Svelte 等架構)
  • API 請求參數與回傳資料的型別推導

工作流程圖(概念說明):

flowchart TD
    %% 後端部分
    subgraph Backend["後端 schema 定義"]
        SchemaFile["schema.graphql"]
        TypeDefs["type User {...}<br />type Query {...}"]
        SchemaFile --> TypeDefs
        TypeDefs --> TypeSystem["型別系統"]
        TypeDefs --> EntryPoint["操作系統 (定義入口)"]
    end

    %% API 服務
    API["API 服務"]
    Backend --> API

    %% 前端部分
    subgraph Frontend["前端查詢請求檔案"]
        QueryFile["getUser.graphql"]
        QueryOp["query GetUser { ... }"]
        QueryFile --> QueryOp
        QueryOp --> FrontOp["前端發送的操作"]
    end

    API --> Frontend

    %% Codegen 部分
    Codegen["搭配 Codegen"]
    Frontend --> Codegen
    Backend --> Codegen

    %% 輸出
    Safety["產出型別安全函式<br />(如:useGetUserQuery)"]
    Codegen --> Safety

    %% 樣式設定
    classDef backend fill:#f9f9f9,stroke:#333,stroke-width:1px
    classDef frontend fill:#f9f9f9,stroke:#333,stroke-width:1px
    classDef file fill:#fffde7,stroke:#fbc02d,stroke-width:1px
    classDef service fill:#e1f5fe,stroke:#0288d1,stroke-width:1px
    classDef output fill:#e8f5e9,stroke:#2e7d32,stroke-width:1px

    class Backend backend
    class Frontend frontend
    class SchemaFile,TypeDefs,QueryFile,QueryOp file
    class API,Codegen service
    class Safety output

📄 .graphql 原始查詢檔案範例(前端撰寫)

# user.graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
  }
}

Codegen 自動產出結果(部分範例)

// 自動產生的 hook 函式與型別
export function useGetUserQuery(
  options: QueryHookOptions<GetUserQuery, GetUserQueryVariables>
) {
  return useQuery<GetUserQuery, GetUserQueryVariables>(
    GetUserDocument,
    options
  );
}

實際用法:像操作 React Query 一樣簡單

const { data, loading, error } = useGetUserQuery({
  variables: { id: "123" }
});

這段程式碼背後的查詢、型別檢查與 API 串接邏輯,都由 Codegen 自動幫你產生,讓你:

  • 🔐 自動享有回傳資料的完整型別提示
  • 🧼 避免拼錯欄位名、錯用型別等常見錯誤
  • 🧩 自動對應後端 schema 的資料格式,若 schema 改動能即時發現錯誤

🔧 實務應用:常見工具鏈整合

技術整合方式說明
Apollo Client可產出 useXxxQuery / useXxxMutation Hook
urql可搭配產生 useQuery, useMutation 變數與型別
React自動產出符合 React 結構的 Hook 函式與型別
TypeScript查詢變數與回傳結果全程型別檢查

常見問題 FAQ:關於 .graphql 你可能會有的疑問

Q1:前端的 .graphql 檔案會送到後端執行嗎?

會的,但並不是直接「送檔案」過去。

前端的 .graphql 檔案中撰寫的是查詢語法(Query / Mutation / Subscription)。

這些查詢會在程式執行時被讀取,轉換成一段純文字的 GraphQL 查詢字串,然後由前端程式發送成 HTTP 請求(大多是 POST)。

範例:

POST /graphql
Content-Type: application/json

{
  "query": "query GetUser($id: ID!) { user(id: $id) { id name } }",
  "variables": { "id": "123" }
}

後端的 GraphQL Server 會接收到這段查詢文字,根據事先定義好的 Schema 和 Resolver,解析並執行對應的邏輯後回傳結果。

Q2:所有前端查詢都一定要寫在 .graphql 檔案嗎?

🟡 不一定,但這樣做有許多好處。

你可以選擇:

  • 將查詢語法直接寫在 JS/TS 程式碼中(稱為 inline query):
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
    }
  }
`;
  • 或是獨立拆成 .graphql 檔案 並集中管理:
# user.graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

📌 建議在中大型專案中,將查詢獨立為 .graphql 檔案,原因包括:

原因好處
結構清晰、模組化每個查詢檔可依功能命名,便於查找與重用
可搭配 Codegen 使用自動產出型別與查詢函式,提高開發效率與安全
更容易做 lint / 測試.graphql 檔案可被靜態分析工具讀取,便於執行驗證或產出文件

Q3:可以在同一個 .graphql 檔案中寫多個 query / mutation 嗎?

可以,只要每個操作定義都有唯一名稱即可。

範例:

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

mutation UpdateUser($id: ID!, $name: String!) {
  updateUser(id: $id, name: $name) {
    id
    name
  }
}

這樣做在小型專案或同一功能模組中很方便,但如果查詢越來越多,建議一個檔案一個操作,方便維護與追蹤。

💡 補充:Codegen 也支援多操作一檔的模式,只要名稱不衝突就能正確產出多個對應函式與型別。

結語:.graphql 是前後端共同使用的語言標準

.graphql 檔案不只是純文字,更是一種溝通協議與開發工具。

透過 .graphql

  • 後端 定義 API 規則、資料模型與可操作的端點
  • 前端 撰寫實際查詢語法,清楚描述需要什麼資料、如何操作
  • 工具鏈(如 Codegen) 則讓這兩端自動對齊、型別同步,減少錯誤與溝通成本

🎯 初學者只要掌握三個核心觀念:

  1. .graphql 檔案可以分為 Schema(後端)與 Operation(前端)兩種用途
  2. 前端查詢寫在 .graphql 檔案中更結構化,也能搭配工具產出型別與查詢函式
  3. Codegen 是讓這一切發揮威力的關鍵橋樑,將查詢轉為型別安全的開發體驗

這就是為什麼說:「學好 .graphql,是做好 GraphQL 開發的第一步。」🧩

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *