當你在學 GraphQL 時,可能會有這樣的疑問:
- 「GraphQL Query 是怎麼從前端送到資料庫,最後再返回結果的?」
- 「前端、後端、資料庫,各自負責哪一段?」
如果你是初學者,對這些過程的印象可能只有一句話:「前端送 Query,後端回傳 JSON」。
但實際上,這中間經歷了好幾個步驟,每個層面(前端 / 後端 / 資料庫)都扮演著不同角色。
準備階段:Schema 與執行引擎的建立
這個階段是 GraphQL 服務啟動前的「基礎建設」,目的是讓後端伺服器先準備好「能理解請求、能對應資料、能正常執行」的環境。
| 步驟 | 說明 | 負責層面 |
|---|---|---|
| 1. 定義 Schema | 設計 Query、Mutation、Type 等,描述「能查什麼資料、怎麼操作」。 | 後端(由工程師手動撰寫或工具自動生成) |
| 2. 建立型別系統(Type System) | 從文字 Schema 轉成型別物件,提供「型別驗證」的依據。 | 後端(在 GraphQL Server 啟動時完成) |
| 3. 綁定 Resolver(資料獲取邏輯) | 每個欄位都需要綁定一個取得資料的方法(Resolver)。例如 user.name 需要對應一個函式告訴後端去哪抓名字。 | 後端 |
| 4. 初始化執行引擎 | 啟動 GraphQL Server,將 Schema 與 Resolver 整合。 | 後端 |
✅ 時間點:
通常發生在 專案啟動或部署時,前端與資料庫幾乎不參與。
✅ 核心觀念:
把它想像成 「設計一個大型問答系統」:
- Schema 就是「問題清單」:告訴使用者「你可以問什麼問題」。
- Resolver 就是「回答邏輯」:當有人問問題時,你要去哪查答案。
- 執行引擎 就是「主持人」:負責接收問題、轉交給對的人回答。
定義 Schema:設計「能問什麼、能操作什麼」
🔹 是什麼?
Schema 就像是 API 的契約,明確定義:
- 有哪些 Query 可以查詢資料?(例如
user(id: ID)) - 有哪些 Mutation 可以修改資料?(例如
createPost(title: String)) - 每個欄位長什麼樣?有什麼型別?(例如
user.name是String,user.age是Int)
🔹 為什麼要這麼做?
- 前端能清楚知道「可以問什麼問題」。
- 後端能依 Schema 檢查「問題合不合法」。
- Schema 也能作為團隊內的溝通語言(尤其是前後端協作)。
🔹 誰負責?
✅ 後端工程師(手動撰寫 Schema)
✅ 或 自動化工具(例如 Hasura、Prisma 會自動根據資料庫結構生成 Schema)
🔹 舉例:
type User {
id: ID
name: String
age: Int
}
type Query {
user(id: ID): User
}這段 Schema 告訴前端:「你可以問 user,他會回傳一個 User 型別,裡面有 id、name、age 三個欄位」。
建立型別系統(Type System):讓伺服器「看得懂」Schema
🔹 是什麼?
在 GraphQL 中,Schema 只是「一份文件」,用類似文字描述的語法寫成,看起來像這樣:
type User {
id: ID
name: String
age: Int
}這份 Schema 是給人看的,讓前端或其他工程師知道「可以問什麼問題」。
但伺服器本身看不懂這種文字結構,它需要一個「程式能理解的結構化資料」,才能:
- 在收到 Query 時進行驗證(例如
user.age是不是 Int?有沒有這個欄位?) - 在執行時知道要如何比對資料結構
👉 這就是 型別系統(Type System) 的角色。
它會把文字 Schema「翻譯」成一組 JavaScript/其他語言的物件結構,就像這樣:
// 以 JavaScript 為例(簡化)
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
age: { type: GraphQLInt }
}
})這個「型別物件」才是伺服器執行時真正使用的資料結構。
🔹為什麼要這麼做?
- 讓伺服器能「懂」這些欄位是什麼
- Schema 是「人類語言」,Type System 是「程式語言」。
- 沒有型別系統,伺服器收到 Query 時,無法理解
user.age是一個數字還是一段文字。
- 執行前就能擋掉錯誤(提高安全性)
例如前端不小心傳了這個 Query:
{
user {
age
hobby
}
}但 User 型別根本沒定義 hobby,型別系統會在「執行前」就丟錯,避免浪費資源去查資料庫。
- 讓工具可以做自動補全、文件生成
- 開發時你在 VS Code 打
user.時,IDE 能自動跳出name、age等欄位,就是因為型別系統有明確的型別定義。
- 為執行階段做準備
- 執行階段的「驗證(Validation)」與「執行計畫(Execution Planning)」都依賴型別系統,沒有型別系統,這些步驟無法進行。
🔹 什麼時候發生?
✅ GraphQL Server 啟動時自動完成,一次建立好就不會在每次請求時重新生成。
🔹 誰負責?
✅ 後端(伺服器層處理)
綁定 Resolver(資料獲取邏輯):告訴後端去哪查答案
🔹 是什麼?
Resolver 是真正負責「取資料」的函式,每個 Schema 欄位都需要對應一個 Resolver。
例如:
user.name→ 可能要查資料庫中的users表user.posts→ 可能要呼叫另一個 API
🔹 為什麼要這麼做?
- Schema 只定義「你可以問什麼」,Resolver 才負責「怎麼回答」。
- 沒有 Resolver,即使 Schema 定義再完整,也沒辦法真正拿到資料。
🔹 誰負責?
✅ 後端工程師撰寫,有些工具(Hasura、Prisma)也會幫你生成預設的 Resolver。
🔹 簡單舉例:
const resolvers = {
Query: {
user: (_, args, context) => {
return db.users.findById(args.id)
}
}
}當前端問 user(id: 1) 時,這個 Resolver 就會查資料庫中的 users 表。
初始化執行引擎:讓伺服器開始運作
🔹 是什麼?
把 Schema 和 Resolver 整合到 GraphQL 執行引擎,讓伺服器能接收 Query、解析、執行。
🔹 為什麼要這麼做?
- 如果沒有執行引擎,前端送來的 Query 根本沒地方跑。
- 執行引擎是後端的「大腦」,負責後面所有查詢請求的解析、驗證、執行。
🔹 誰負責?
✅ 後端
通常是後端框架(Apollo Server、Yoga、Hasura)幫忙完成。
🔹 簡單理解:
可以把它想像成「把廚房開起來,讓廚師(Resolver)可以開始接單做菜」。
✅ 關鍵觀念總結
- 前端:此階段完全不參與,因為前端只負責「問問題」,不管「問題怎麼設計」。
- 資料庫:只要「資料結構存在」即可,並不直接參與這些後端準備工作。
執行階段:每次查詢請求的完整流程
這是 前端發送一個 Query 後,從送出到收到結果的完整過程。
它是 前端 → 後端 → 資料庫 串聯合作的結果。
可以把它想像成:
前端是「顧客」,後端是「餐廳廚房」,資料庫是「食材倉庫」,從點餐、備餐到送餐回來,每一步都有它的工作流程。
整體流程步驟
| 步驟 | 說明 | 背後發生了什麼 | 負責層面 |
|---|---|---|---|
| 1. 接收查詢請求(Receive Query) | 前端發送 GraphQL Query(通常是 HTTP POST),後端接收。 | ✅ 前端通常使用 Apollo Client、Relay 或 Fetch 送出請求。 ✅ 後端接收到一個「純文字 Query」字串,還不理解它的結構。 | 前端 → 後端 |
| 2. 解析(Parsing) | 將 Query 字串轉成 AST(抽象語法樹)。 | ✅ 後端會用內建的 GraphQL Parser,將文字 Query 解析成「物件化結構」。 ✅ AST 是一個「樹狀結構」,每個節點代表 Query 的一部分(例如欄位、引數)。 ✅ 如果文字本身有語法錯誤(少了括號、拼錯欄位),這一步就會報錯。 | 後端 |
| 3. 驗證(Validation) | 確認 Query 是否符合 Schema 與型別系統。 | ✅ 解析完後,後端會拿 AST 去跟型別系統比對: ‣ 查詢的欄位是否存在? ‣ 傳入的引數型別對不對? ✅ 這一步是在「執行前」攔截錯誤,避免浪費資料庫資源。 | 後端 |
| 4. 執行計畫(Execution Planning) | 決定呼叫哪些 Resolver、順序及依賴關係。 | ✅ GraphQL 的執行引擎會分析: ‣ 先執行哪些欄位?哪些欄位可以平行查詢? ‣ 需要先取得什麼值再執行後續查詢(例如 user.id 再查 posts)。✅ 這步有點像「擬定工作排程」。 | 後端 |
| 5. 執行(Execution) | 呼叫對應的 Resolver,Resolver 會向資料庫或其他服務請求資料。 | ✅ 每個欄位都會被分配給對應的 Resolver。 ✅ Resolver 可以: ‣ 查資料庫(最常見) ‣ 呼叫外部 API ‣ 執行商業邏輯(例如資料轉換) | 後端 → 資料庫 |
| 6. 組裝結果(Result Assembly) | 將 Resolver 返回的資料,依 Query 結構組成 JSON。 | ✅ 後端會按照 Query 的「樹狀結構」組裝回應資料。 ✅ 例如 Query 問了 user.name 和 user.posts.title,回應就會對應成相同層次的 JSON。 | 後端 |
| 7. 回應前端(Return Response) | 將 JSON 結果回傳給前端,前端渲染畫面。 | ✅ 後端會用 HTTP Response 回傳 JSON。 ✅ 前端接到後會更新畫面,可能用 React/Vue 等框架渲染資料。 | 後端 → 前端 |
假設前端送出這個 Query:
query {
user(id: 1) {
name
posts {
title
}
}
}這個 Query 想要的資料是:
👉 id=1 的使用者 (user)
👉 他的名字 (name)
👉 他的文章標題 (posts.title)
整個過程就像一個 「顧客點餐 → 餐廳廚房作業 → 倉庫取貨 → 廚房擺盤 → 送餐 → 顧客用餐」的故事。
以下每一步都會同時講劇情(比喻)與真實發生的技術行為。
前端:顧客點餐(提出需求)
劇情版:
你是餐廳裡的顧客,你把你想吃的東西寫在一張「購物清單」上,交給廚房:
👉「我要 id=1 的使用者,他的名字,還有他發表過的文章標題。」
技術版:
- 前端(React、Vue 等框架)組裝這段 Query,並用 Apollo Client、Relay 或 fetch 透過 HTTP POST 傳送給後端的 GraphQL 伺服器。
- 在這個階段,後端接收到的只是一個「純文字 Query」。
注意:前端 不需要也不會知道這些資料怎麼查,它只要「提出需求」並等回應。
後端:廚房備餐(理解訂單、決定流程)
後端就是整個流程的「總指揮」,像餐廳裡的廚房,負責看懂訂單、安排怎麼做、然後去拿食材。
檢查訂單格式(Parsing + Validation)
劇情版:
廚師接到訂單後,第一件事是「檢查訂單寫得對不對」:
- 你寫的菜名(欄位名)有沒有拼錯?
- 你要求的數量(參數型別)合不合理?
如果你點了「菜單上沒有的菜」(例如 user.hobby),廚師會直接退回訂單,連做菜都不會開始。
技術版:
- Parsing(解析): 後端先把 Query 轉成 AST(抽象語法樹),讓 Query 從「文字」變成「結構化資料」。
- AST 例子:
user是一個節點,posts是它的子節點。 - Validation(驗證): 後端用 AST 比對型別系統,確認:
user是否存在於 Schema?id的型別是不是 Int?posts是否真的屬於user?
如果在這一步出錯,後端直接回傳錯誤 JSON,不會進入後續階段。
安排做菜順序(Execution Planning)
劇情版:
廚師接著開始「規劃怎麼煮」:
- 先煮湯(先查
user) - 等湯煮好拿到
user.id,再煮附餐(查posts)
技術版:
- GraphQL 執行引擎會分析欄位之間的依賴關係:
user必須先查出來 → 因為posts的查詢需要user.id。- 但如果 Query 有其他獨立欄位(例如
comments),它們可以平行查詢以提升效能。
做菜取食材(Execution)
劇情版:
廚房開始行動,每道菜都由一個專門的廚師負責。
- 負責主菜的廚師跑去倉庫(資料庫)拿到使用者資訊。
- 再由另一個廚師拿著
user.id去倉庫拿這個使用者發表過的文章。
技術版:
- GraphQL 會呼叫對應的 Resolver(資料獲取函式):
user的 Resolver 可能執行 SQL 查詢:SELECT * FROM users WHERE id=1posts的 Resolver 再用user.id查:SELECT title FROM posts WHERE user_id=1
- Resolver 不只查資料庫,也可以呼叫其他 API 或執行商業邏輯。
資料庫:倉庫出貨(只提供原料)
劇情版:
倉庫只負責把食材搬給廚房,不會幫忙煮菜或擺盤。
- 廚師說「幫我拿
id=1的雞肉(user)」 → 倉庫就搬來 - 廚師說「幫我拿雞肉的附餐食材(posts)」 → 倉庫再搬來
技術版:
- 資料庫只根據 SQL 查詢回傳原始資料,並不知道最終的 JSON 結構。
- 例如:
- 查
users表 →{id:1, name:"Tom"} - 查
posts表 →[{title:"Hello World"}]
- 查
後端:組裝餐點並上桌(Result Assembly + Return)
劇情版:
廚師收到倉庫的食材後,把它們擺盤成你原本訂的樣子:
- 把「主菜(user)」放在盤子中央
- 把「附餐(posts.title)」放在旁邊
技術版:
- 後端按照 Query 的樹狀結構組裝資料。
- 這是 GraphQL 與 REST API 最大的不同之一:
回傳結果的結構會完全依照你 Query 的寫法。
最終回傳給前端的 JSON 會長這樣:
{
"data": {
"user": {
"name": "Tom",
"posts": [
{ "title": "Hello World" }
]
}
}
}前端:顧客吃到餐點(更新畫面)
劇情版:
顧客(前端)接到廚房送來的餐點後,擺在桌上,開始享用。
技術版:
- 前端接到 JSON 後,更新畫面。
- 如果用 Apollo Client,它會自動把結果寫入快取,並觸發畫面重新渲染,讓頁面顯示「Tom」的名字與文章標題。
關鍵觀念總結(重新強化記憶)
- 前端 → 顧客:
只管「提出需求」與「更新畫面」,完全不關心資料怎麼取得。 - 後端 → 廚房:
是整個流程的「大腦」與「指揮者」,負責檢查、規劃、拿資料、組裝結果。 - 資料庫 → 倉庫:
只提供「原料」;它不理解 Query,也不負責組裝 JSON。
責任分工一覽表
這張表格告訴你 「前端、後端、資料庫」各自的工作範圍。
但為了讓你不只是死背表格,下面我會逐步說明 每一階段、每一個步驟為什麼這樣分工,以及背後實際發生的事。
完整責任分工表
| 階段 | 步驟 | 前端 | 後端 | 資料庫 |
|---|---|---|---|---|
| 準備階段 | 定義 Schema | ❌ | ✅(撰寫或自動生成 Schema) | ❌(只要確保資料結構存在即可) |
| 建立型別系統 | ❌ | ✅(把文字 Schema 轉成程式能理解的型別物件) | ❌ | |
| 綁定 Resolver | ❌ | ✅(撰寫 Resolver,定義怎麼取得資料) | ❌ | |
| 初始化執行引擎 | ❌ | ✅(啟動 GraphQL 伺服器,結合 Schema 和 Resolver) | ❌ | |
| 執行階段 | 接收查詢請求 | ✅(發送 Query) | ✅(接收並準備處理) | ❌ |
| Parsing | ❌ | ✅(將 Query 文字轉成 AST) | ❌ | |
| Validation | ❌ | ✅(比對 Schema,確認欄位與型別正確) | ❌ | |
| Execution Planning | ❌ | ✅(決定 Resolver 執行順序和平行或依賴關係) | ❌ | |
| Execution | ❌ | ✅(呼叫 Resolver) | ✅(執行 SQL 查詢或讀取原始資料) | |
| 組裝結果 | ❌ | ✅(按照 Query 結構組合 JSON) | ❌ | |
| 回應前端 | ✅(接收結果、更新畫面) | ✅(回傳 JSON 結果) | ❌ |
為什麼要這樣分工?
準備階段(後端主導)
這個階段完全是「後端的世界」,前端和資料庫幾乎不參與。
- 定義 Schema → 為什麼是後端負責?
- Schema 是「API 的菜單」,只有後端知道「有哪些資料可以查」。
- 前端只會依 Schema 查資料,沒資格改菜單;資料庫只要有相應的欄位就好。
- 建立型別系統 → 為什麼前端不需要?
- 型別系統是「伺服器內部的驗證機制」。
- 前端並不直接參與伺服器內部的邏輯,只是消費者。
- 綁定 Resolver → 為什麼資料庫不參與?
- Resolver 是一段「後端邏輯」,它可能呼叫資料庫,也可能呼叫其他 API。
- 資料庫只是個原始倉庫,它不會決定「查什麼」。
- 初始化執行引擎 → 為什麼全是後端?
- GraphQL 伺服器是後端的核心運行引擎,前端只是使用者,資料庫只是被動供應商。
執行階段(前端、後端、資料庫串聯)
這個階段就是一次真實的 Query 從送出到返回結果的全過程。
- 接收查詢請求 → 前端和後端共同參與
- 前端負責組裝 Query 並發送。
- 後端負責接收並準備處理。
- Parsing、Validation、Execution Planning → 為什麼只有後端?
- 這些屬於伺服器內部的工作:
✅ Parsing:把文字 Query 轉成 AST
✅ Validation:用型別系統驗證欄位
✅ Execution Planning:決定執行順序 - 前端只是顧客,資料庫只是被動供應商,兩者都不需要參與。
- Execution → 為什麼後端和資料庫都要參與?
- 後端(Resolver)像「中間人」,它決定該怎麼查資料。
- 資料庫負責真正去讀取資料(SQL 查詢)。
- 前端在這一步完全不參與,因為它根本不會直接操作資料庫。
- 組裝結果 → 為什麼只有後端?
- GraphQL 最大的特色就是「根據 Query 的結構返回資料」。
- 這需要後端自己組裝 JSON,資料庫只提供原始資料,不會組裝成前端要的結構。
- 回應前端 → 前端和後端共同參與
- 後端:將結果打包成 JSON 回傳
- 前端:接收到結果後,更新畫面(例如 React 重新渲染組件)。
初學者快速記憶法
✅ 一句話記住三層分工:
- 前端:只負責 「問問題」與「更新畫面」
- 後端:是 「大腦 + 執行者」,負責檢查、決策、拿資料、組裝
- 資料庫:只是一個 「原料倉庫」,負責按照需求提供原始資料
✅ 口訣版:
「前端提問、後端思考、資料庫供應」
各階段常用工具對應表
在實際專案中,準備階段與執行階段通常會使用許多現成工具或框架,幫助你快速生成 Schema、簡化請求流程、管理快取,甚至自動生成型別。
下面整理了一份 「每個步驟 → 對應工具 → 用途」的詳細清單。
準備階段(Schema 與執行引擎的建立)
| 步驟 | 常用工具 | 用途與優點 |
|---|---|---|
| 定義 Schema | ✅ Hasura | 自動根據資料庫結構生成 GraphQL Schema,快速搭建 API,適合不想手動寫 Schema 的專案。 |
| ✅ Prisma | 基於資料庫模型自動生成 GraphQL Schema,並附帶 ORM 功能,適合需要控制資料庫操作的專案。 | |
| ✅ Apollo Server | 適合手動撰寫 Schema 的專案,可靈活定義 Query、Mutation、Type。 | |
| 建立型別系統 | ✅ GraphQL.js | GraphQL 官方底層實作,會自動將文字 Schema 轉換成型別物件(Type System)。大多數框架(Apollo、Yoga)也基於此。 |
| ✅ Nexus | 透過程式碼生成型別系統,比直接寫 SDL(Schema Definition Language)更有型別安全,適合 TypeScript 專案。 | |
| 綁定 Resolver | ✅ Apollo Server | 提供簡單的方式撰寫 Resolver 並綁定到 Schema。 |
| ✅ Hasura | 自動生成 Resolver;若需要自訂,也可透過 Remote Schema 或 Action 實現。 | |
| ✅ Prisma + Nexus | Prisma 幫助快速查資料庫,Nexus 提供型別安全的 Resolver 撰寫方式。 | |
| 初始化執行引擎 | ✅ Apollo Server | 最常見的 GraphQL Server 框架,整合 Schema、Resolver、型別系統,社群資源豐富。 |
| ✅ Yoga GraphQL | 輕量化的 GraphQL Server,快速上手,適合小型專案或初學者。 | |
| ✅ Hasura | 自動化 GraphQL Server,開箱即用,幾乎不需要額外撰寫後端程式碼。 |
執行階段(從請求到回應的完整流程)
| 步驟 | 常用工具 | 用途與優點 |
|---|---|---|
| 1. 撰寫 Query / Mutation | ✅ Apollo Client(前端) | 提供 gql 標記函式,支援撰寫 Query / Mutation,並結合型別提示。 |
| ✅ GraphQL Code Generator(前端) | 自動生成對應型別與 Hook,避免手動維護查詢結果型別。 | |
| 2. 組裝變數與 HTTP / WS 請求 | ✅ Apollo Client(前端) | 自動打包 Query、變數,並透過 HTTP / WebSocket 發送請求。 |
| ✅ Relay(前端) | Facebook 出品,適合大型專案,強化查詢與快取優化。 | |
| ✅ Urql(前端) | 輕量化 GraphQL 前端客戶端,適合中小型專案。 | |
| 3. 發送請求 | ✅ Apollo Client / Relay(前端) | 負責發送 HTTP POST 或 WebSocket(即時訂閱)。 |
| ✅ fetch / Axios(前端) | 若不需要完整快取管理,可直接用這些低階 API 發送請求。 | |
| 4. 接收查詢請求 | ✅ Apollo Server(後端) | 監聽 HTTP 請求並接收 Query,是最常見的 GraphQL 伺服器框架。 |
| ✅ Hasura(後端) | 自動接收 Query,無需手動配置路由。 | |
| 5. Parsing(解析) | ✅ GraphQL.js(後端) | 將 Query 文字轉換成 AST(抽象語法樹),大多數伺服器底層都用這個。 |
| 6. Validation(驗證) | ✅ GraphQL.js(後端) | 比對 Schema 與型別系統,提前攔截 Query 錯誤。 |
| 7. Execution Planning(執行計畫) | ✅ Apollo Server / Hasura(後端內建) | 自動決定 Resolver 的執行順序(例如先查 user 再查 posts)。 |
| 8. Execution(呼叫 Resolver) | ✅ Prisma(後端) | 作為 ORM,幫助 Resolver 快速查資料庫。 |
| ✅ Hasura(後端) | 自動執行 SQL 查詢,直接從資料庫取資料。 | |
| ✅ Axios / Fetch(後端) | 如果 Resolver 需要呼叫外部 REST API,可用這些工具。 | |
| 9. 組裝結果(Result Assembly) | ✅ GraphQL 執行引擎(後端內建) | 自動根據 Query 的樹狀結構組裝 JSON,開發者通常不需要手動處理。 |
| 10. 回傳結果(Return Response) | ✅ Apollo Server / Hasura(後端) | 將組裝好的 JSON 以 HTTP 回傳前端。 |
| 11. 接收結果並更新快取 | ✅ Apollo Client(前端) | 自動將結果寫入快取,並支援快取同步更新畫面。 |
| ✅ Relay(前端) | 高度優化快取與狀態管理,適合大型專案。 | |
| 12. 渲染畫面 | ✅ React / Vue(前端) | 前端框架會根據快取或結果資料,重新渲染對應元件。 |
初學者快速選擇建議
如果你是 初學者,可以直接採用以下組合:
- 全自動快速搭建(低代碼)
→ Hasura + Apollo Client
✅ Hasura 幫你自動處理 Schema、Resolver、執行引擎;前端只需用 Apollo Client 查資料。 - 靈活手動控制(適合學習原理)
→ Apollo Server + Apollo Client
✅ 後端手動撰寫 Schema、Resolver;前端同樣用 Apollo Client 發送查詢並管理快取。 - 強型別 + 適合中大型專案
→ Prisma + Nexus + Apollo Server(後端) + Apollo Client(前端)
✅ 適合需要高度型別安全與靈活控制的專案。