Logo

新人日誌

首頁關於我部落格

新人日誌

Logo

網站會不定期發佈技術筆記、職場心得相關的內容,歡迎關注本站!

網站
首頁關於我部落格
部落格
分類系列文

© 新人日誌. All rights reserved. 2020-present.

本文為「GraphQL 從 0 到 1 系列」系列第 8 篇

GraphQL 伺服器架構指南:初學者入門

最後更新:2025年6月10日架構

當你剛接觸 GraphQL 時,可能會被它的查詢語法所吸引:

你可以一次查多個欄位、不必再開多個 API、甚至還能只取用你要的資料欄位,看起來非常高效又簡潔。

再進一步學習後,你可能已經理解了幾個核心名詞:

Schema 定義查詢能要什麼資料、Resolver 負責回傳資料、Query 是前端送出的請求。

但當你開始動手實作,或遇到跨團隊協作時,常會出現一個困惑:

「這些東西到底是怎麼串起來的?」

「為什麼我寫了 Schema,但資料沒回來?」

「這個 Resolver 放在哪裡?是誰在呼叫它?」

這些問題其實都來自於:

你對於 GraphQL 背後的伺服器架構與執行流程還不夠清楚。

GraphQL 和 REST 的最大差異,不只是語法上的不同,更是在執行模型上的設計哲學差異。

REST 傾向把邏輯集中在後端每一個 Endpoint;

但 GraphQL 則強調彈性、模組化與分層設計,把查詢規格(Schema)與查詢實作(Resolver)做了明確拆分。

這種拆分帶來了極大的彈性,但也提高了理解門檻。

因此,若你想更上一層樓,不再只是「會寫 Query」或「知道什麼是 Resolver」,而是能獨立開發一個完整的 GraphQL API,甚至與前後端團隊順利協作,那麼:

✅ 了解 GraphQL 伺服器的整體架構運作方式,就是你必經的關鍵一步。

本篇文章將從最基礎的角度切入,幫助你了解:

  • 一個 GraphQL Server 的核心元件有哪些?
  • Query 是如何從前端一路跑到資料庫,再回傳資料?
  • 手寫伺服器與自動化工具(如 Hasura)有什麼差別?
  • 在大型系統裡,GraphQL Server 要部署在哪裡?(One Server Model vs Gateway Model)

這些觀念不只是幫你「學會 GraphQL」,而是幫你具備開發、部署、選型的判斷力。

準備好了嗎?我們從三大核心角色開始。

GraphQL Server 的三大核心角色

在 GraphQL 的世界裡,一個伺服器不只是處理查詢請求而已。

它其實是一個分工明確的系統,主要由三個核心元件構成,分別負責「定義、解析、執行」的工作。

元件功能說明
Schema負責定義 API 的規格,包括你可以查詢什麼資料、有哪些欄位、參數格式、型別結構。例如你可以查詢 User 類型,它包含 id、name、posts 等欄位。
Resolver每個欄位背後的實作邏輯都由 Resolver 處理。Resolver 會根據查詢的參數去資料庫撈資料、處理邏輯、甚至整合外部 API,例如從資料庫查出 user.name。
執行引擎(例如 ApolloServer)是負責協調一切的核心角色,從接收查詢、解析查詢語法、比對 Schema、呼叫對應 Resolver,再組裝結果並回傳。
功能說明負責定義 API 的規格,包括你可以查詢什麼資料、有哪些欄位、參數格式、型別結構。例如你可以查詢 User 類型,它包含 id、name、posts 等欄位。
功能說明每個欄位背後的實作邏輯都由 Resolver 處理。Resolver 會根據查詢的參數去資料庫撈資料、處理邏輯、甚至整合外部 API,例如從資料庫查出 user.name。
功能說明是負責協調一切的核心角色,從接收查詢、解析查詢語法、比對 Schema、呼叫對應 Resolver,再組裝結果並回傳。

可以把這三者的關係比喻為:

  • Schema 是 API 的說明書(你可以查什麼)
  • Resolver 是 API 的執行者(資料怎麼來)
  • 執行引擎是總指揮官(誰該做什麼、結果怎麼整合)

整體資料流程圖:Query 是怎麼跑的?

當你學會寫出以下這樣的 GraphQL 查詢語法時:

query {
  user(id: "123") {
    name
    email
  }
}

你可能會好奇:「這一段文字,實際上是怎麼變成我看到的資料?GraphQL 在背後到底做了什麼?」

事實上,這整個過程包含了多個階段,每個階段都扮演著關鍵角色:

詳細流程解析:GraphQL 查詢從送出到回傳的每一步

當你在前端畫面上點擊按鈕、或寫下這樣的查詢時:

query {
  user(id: "123") {
    name
    email
  }
}

你其實啟動了一段完整的查詢生命週期。這段看起來簡單的文字,會經歷以下五大階段,才會變成畫面上你看到的資料。

flowchart TD
    A[Client] -->|送出查詢| B[GraphQL Server]
    B -->|執行引擎| C[Schema]
    C -->|比對查詢結構| D[Resolver]
    D -->|逐欄位處理資料獲取| E[資料來源 DB / API / 快取等]
    E -->|執行引擎組合回傳資料| F[JSON 格式]
    F -->|回傳結果| A

1️⃣ 前端送出查詢請求(Query)

查詢可以來自:

  • 使用者點擊頁面上的按鈕,觸發 React 或 Vue 等框架裡的 API 請求
  • 開發者在程式中呼叫 Apollo Client、Relay、urql 等工具庫
  • Postman 或 GraphQL Playground 等工具的手動測試

這些工具最終都會將查詢內容(例如上面的 user(id: "123"))以 HTTP 請求(通常是 POST /graphql)的形式送到 GraphQL Server。

✅ 查詢內容的意義

這段查詢代表的意思是:

「請幫我找出 id = 123 的使用者,然後只回傳他的 name 和 email 欄位。」

這就是 GraphQL 的精髓之一:只取你要的資料欄位,其他不拿。

2️⃣ 伺服器收到查詢 → 執行語法解析與 Schema 驗證

伺服器收到這段查詢文字後,並不會立刻查資料,而是先經過兩個重要步驟:

🔹 a. 語法解析(Parsing)

GraphQL Server(如 Apollo Server)會把這段文字解析成一個「抽象語法樹(AST)」結構,就像語言老師幫你分析句子主詞、動詞、受詞一樣,確保這是一個合法的 GraphQL 語句。

🔹 b. Schema 驗證(Validation)

接著會拿這個 AST 跟伺服器中事先定義好的 Schema 進行比對,檢查以下事項:

檢查項目說明
欄位是否存在user 是不是 Query 中允許查詢的欄位?name、email 是否是 User 類型中允許的欄位?
型別是否正確id 是不是應該是字串(ID)?如果你傳成整數,會報錯。
結構是否正確查詢語法有沒有寫錯?花括號 {} 有沒有少寫?欄位是否嵌套正確?
說明user 是不是 Query 中允許查詢的欄位?name、email 是否是 User 類型中允許的欄位?
說明id 是不是應該是字串(ID)?如果你傳成整數,會報錯。
說明查詢語法有沒有寫錯?花括號 {} 有沒有少寫?欄位是否嵌套正確?
❗ 若驗證失敗會怎樣?

GraphQL 是結構嚴謹且強型別的語言,只要有一個欄位拼錯、漏參數、型別不符,就會直接回傳錯誤訊息給前端,而不會進入查資料的階段。

3️⃣ 對照 Schema → 決定呼叫哪些 Resolver

通過語法解析與驗證後,GraphQL Server 就會開始進行實際查詢的規劃。

這時,它會根據 Schema 決定:

  • 查詢的主體是 Query.user(用 id 作為參數)
  • 回傳的是 User 型別,裡面需要的欄位是 name 和 email

你可以把 Schema 想像成 API 的菜單或藍圖,例如:

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

type User {
  name: String
  email: String
}

從這裡,執行引擎知道該怎麼走接下來的流程——它會開始呼叫對應的 Resolver 來處理這些欄位。

4️⃣ 呼叫 Resolver → 執行查詢邏輯

每個欄位(包括最外層的 user 和內層的 name、email)都會對應一個 Resolver 函式,這些函式會負責實際的查詢邏輯。

以下是可能的 Resolver 實作範例:

const resolvers = {
  Query: {
    user: (_, { id }) => db.users.findById(id),
  },
  User: {
    name: (parent) => parent.name,
    email: (parent) => parent.email,
  },
};
✅ 重點說明:
Resolver功能
Query.user根據傳入的 id 去資料庫查出對應的使用者物件
User.name從 parent(也就是剛剛查到的使用者物件)中取出 name
User.email同上,取出 email 欄位
功能根據傳入的 id 去資料庫查出對應的使用者物件
功能從 parent(也就是剛剛查到的使用者物件)中取出 name
功能同上,取出 email 欄位

這裡的每個欄位都會被單獨觸發一次 Resolver,這就是為什麼說 GraphQL 是「欄位為單位」執行。

🔎 注意:如果查詢中包含多層巢狀欄位,GraphQL 會依照欄位階層遞迴地觸發 Resolver,層層往下查。

5️⃣ 整合資料 → 回傳 JSON 結果給前端

當所有 Resolver 都查完資料後,GraphQL Server 的執行引擎會將這些欄位的結果整合起來,組裝成一份標準的 JSON 格式回應,例如:

{
  "data": {
    "user": {
      "name": "Alice",
      "email": "alice@example.com"
    }
  }
}

這份結果會透過 HTTP 回傳給前端應用程式,前端拿到這份資料後,就可以:

  • 顯示在畫面上(如使用者個人資料卡)
  • 存入 local state 或 cache
  • 傳遞給其他元件或做後續處理

🎯 小結:這五步驟構成一套完整的資料查詢生命週期

階段發生位置功能
1. 查詢送出前端發送指定欄位的查詢
2. 語法與驗證GraphQL Server確保查詢語法與結構正確
3. 對照 Schema執行引擎決定要呼叫哪些 Resolver
4. 呼叫 Resolver資料查詢階段逐欄位查詢資料來源
5. 組合結果執行引擎回傳 JSON 結果給前端
發生位置前端
功能發送指定欄位的查詢
發生位置GraphQL Server
功能確保查詢語法與結構正確
發生位置執行引擎
功能決定要呼叫哪些 Resolver
發生位置資料查詢階段
功能逐欄位查詢資料來源
發生位置執行引擎
功能回傳 JSON 結果給前端

這不僅是 GraphQL 的執行過程,也是一種思考方式:每個欄位都是一個執行單位,Schema 是你的查詢藍圖,而 Resolver 是查詢的工人。

GraphQL Server 的開發方式:手寫 vs 自動化

當你決定採用 GraphQL 作為 API 的解決方案時,第一個實作層級的問題就是:

「我要自己從零寫一個 GraphQL Server,還是用工具幫我產生?」

這不只是寫法風格的不同,而是會深刻影響整體開發流程、架構彈性、權限控制、整合能力與未來的維護方式。

這裡我們來介紹兩種常見的開發策略:

  • 手寫型(自訂 Schema 與 Resolver):擁有最高的控制權與彈性,但需要較多的工程經驗。
  • 自動化型(工具自動產生 API):以資料庫為核心、開發快速,適合追求效率與快速迭代的團隊。

手寫型:Apollo Server + 自訂 Schema / Resolver

這種方式是 GraphQL 最原始、最完整的實作模式,由開發者自行撰寫整個 GraphQL Server 的核心三要素:

  • Schema(定義 API 結構)
  • Resolver(欄位查詢邏輯)
  • 執行引擎設定(如 ApolloServer)

這就像你親手打造一間房子,從藍圖、牆面、管線配置通通自己來,雖然花時間,但能夠完全符合你的需求。

🛠 範例:Hello World GraphQL Server

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => "Hello, world!",
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen();

這段程式碼完成了最基本的 GraphQL 查詢能力,只要啟動伺服器,就能從前端查詢 hello 欄位並獲得回應。

🔍 拆解這段程式碼在做什麼?

元件名稱說明與功能
gql\…“使用 gql 這個函式標示 Schema,定義可以查詢哪些欄位
typeDefs存放整體 API 的查詢架構(本例只有一個欄位 hello)
resolvers對應每個欄位的邏輯實作(例如 hello() 回傳一段文字)
ApolloServer整合 Schema 與 Resolver,啟動一個符合 GraphQL 規格的 API
說明與功能使用 gql 這個函式標示 Schema,定義可以查詢哪些欄位
說明與功能存放整體 API 的查詢架構(本例只有一個欄位 hello)
說明與功能對應每個欄位的邏輯實作(例如 hello() 回傳一段文字)
說明與功能整合 Schema 與 Resolver,啟動一個符合 GraphQL 規格的 API

✅ 適合情境

適合情境原因
需要串接多個資料來源(SQL、REST、Firebase、第三方 API)Resolver 可以自由控制查資料的邏輯
有特殊授權/驗證/商業邏輯需求可在 Resolver 加入權限檢查、條件篩選、錯誤處理
想建立自定義查詢語法或複雜資料模型Schema 可以自由設計、不受資料表限制
需要高度擴充性或整合架構(如 BFF、Gateway)這種模式能支撐大型應用的系統邏輯
原因Resolver 可以自由控制查資料的邏輯
原因可在 Resolver 加入權限檢查、條件篩選、錯誤處理
原因Schema 可以自由設計、不受資料表限制
原因這種模式能支撐大型應用的系統邏輯

✅ 優缺點整理

優點說明
彈性極高欄位邏輯、資料來源、授權控制都可自由調整
學習效果佳最能幫助你真正理解 GraphQL 是怎麼查資料的
複雜邏輯好處理特殊欄位轉換、跨表整合、動態權限都可自己實作
說明欄位邏輯、資料來源、授權控制都可自由調整
說明最能幫助你真正理解 GraphQL 是怎麼查資料的
說明特殊欄位轉換、跨表整合、動態權限都可自己實作
缺點說明
開發時間長每個欄位都要手動實作 Resolver
入門門檻高需要理解 Schema、Resolver、執行流程等
易出錯、需自負責任Schema 與資料庫要自己對齊,錯誤處理要自己補
說明每個欄位都要手動實作 Resolver
說明需要理解 Schema、Resolver、執行流程等
說明Schema 與資料庫要自己對齊,錯誤處理要自己補

自動化型:Hasura、PostGraphile 等工具

如果你已經有一份資料庫,而且希望快速讓前端可以查資料,或正在開發原型、內部系統、自動化後台,那麼你可能不想花太多時間寫 Resolver。

自動化型的工具就是為這種需求設計的。

它們的核心理念是:

📌「Schema 不需要你寫,我幫你根據資料表自動產生,還附帶基本的 CRUD Resolver。」

🔧 自動化工具運作原理

以 Hasura 為例:

flowchart TD
    A["資料庫表格建立"] -->|"定義資料結構"| B["Hasura 掃描表格與欄位"]
    B -->|"自動探索關聯"| C["自動產生對應的 Query / Mutation"]
    C -->|"即時生成 API"| D["提供可立即使用的 GraphQL API"]

你只要打開網頁後台介面(Hasura Console),建立資料表,就能立即透過 /graphql 查資料。

✅ 適合情境

適合情境原因
想快速建立 MVP、內部管理後台、BI 工具不需寫 API,也不需後端團隊
專案開發初期,希望盡快驗證資料流或 UI 整合幾分鐘即可完成可用 API
資料結構清晰、邏輯簡單表格欄位就是你的 API 規格
原因不需寫 API,也不需後端團隊
原因幾分鐘即可完成可用 API
原因表格欄位就是你的 API 規格

✅ 優缺點整理

優點說明
快速上手只要有資料庫就能使用
自動產生Schema、Resolver 省下大量重複工
內建權限控制可根據使用者角色設定讀寫範圍
支援即時訂閱資料變動可即時推播(Hasura 特色)
說明只要有資料庫就能使用
說明Schema、Resolver 省下大量重複工
說明可根據使用者角色設定讀寫範圍
說明資料變動可即時推播(Hasura 特色)
缺點說明
彈性有限複雜邏輯需要額外擴充(Actions、Remote Schema)
不利整合多資料來源原生僅支援單一 DB,整合外部 API 較麻煩
高度耦合資料庫結構Schema 變動仰賴 DB 變動,不易抽象拆分邏輯層
說明複雜邏輯需要額外擴充(Actions、Remote Schema)
說明原生僅支援單一 DB,整合外部 API 較麻煩
說明Schema 變動仰賴 DB 變動,不易抽象拆分邏輯層

小結:你該選哪一種?

比較維度手寫型自動化型
開發彈性⭐⭐⭐⭐⭐ 完整控制邏輯與資料來源⭐⭐ 限於資料庫結構
開發效率⭐⭐ 建立需時⭐⭐⭐⭐⭐ 即刻產出 API
入門門檻⭐⭐⭐⭐ 理解概念後才能正確實作⭐ 容易上手,圖形介面操作
適合專案規模中大型系統、API 層整合原型開發、小型工具、資料查詢介面
長期可維護性⭐⭐⭐⭐⭐ 彈性高,適合擴充架構⭐⭐ 需仰賴工具更新與資料庫設計穩定性
手寫型⭐⭐⭐⭐⭐ 完整控制邏輯與資料來源
自動化型⭐⭐ 限於資料庫結構
手寫型⭐⭐ 建立需時
自動化型⭐⭐⭐⭐⭐ 即刻產出 API
手寫型⭐⭐⭐⭐ 理解概念後才能正確實作
自動化型⭐ 容易上手,圖形介面操作
手寫型中大型系統、API 層整合
自動化型原型開發、小型工具、資料查詢介面
手寫型⭐⭐⭐⭐⭐ 彈性高,適合擴充架構
自動化型⭐⭐ 需仰賴工具更新與資料庫設計穩定性

GraphQL Server 在系統中的部署角色:單一伺服器 vs Gateway

當你完成一個 GraphQL Server 的開發,不論是手寫還是自動產生,下一個關鍵問題是:

🔧「我要怎麼把這個 Server 部署到整體系統中?它是獨立應用?還是協調者?」

這就像蓋好一座建築後,要決定它的角色是住宅、商辦,還是轉運中心。

在系統架構裡,我們會根據業務規模、團隊結構與資料來源多樣性,選擇不同的部署模型。最常見的有兩種:

模式一:One Server Model(單一應用伺服器)

在這種模式下,GraphQL Server 就是你的應用核心。它負責:

  • 對外提供 API
  • 對內直接查資料庫
  • 同時處理業務邏輯、驗證、權限等功能

簡單來說:前端查資料 → GraphQL Server → 直接查資料庫 → 回傳結果。

📌 架構圖:

flowchart TD
    A[Client] -->|查詢| B[GraphQL Server]
    B -->|資料操作| C[Database / Cache]

你在開發中寫的 Resolver,會直接呼叫 ORM(如 Prisma、Sequelize)或查 SQL,沒有其他中介層。

✅ 特點:

面向說明
部署簡單單一 Server 即可對外服務
查詢速度快不經過中繼層,直連資料庫
控制彈性高授權、日誌、驗證機制都能完全掌握
運維壓力集中所有責任都由一台 Server 負責
說明單一 Server 即可對外服務
說明不經過中繼層,直連資料庫
說明授權、日誌、驗證機制都能完全掌握
說明所有責任都由一台 Server 負責

✅ 適用情境:

適用場景原因
新創產品或 MVP架構單純、速度快,快速驗證產品
管理後台系統大多查單一資料庫、邏輯簡單
全端開發前後端由一人或一小組維護,溝通成本低
尚未使用微服務無須考慮服務分拆與整合問題
原因架構單純、速度快,快速驗證產品
原因大多查單一資料庫、邏輯簡單
原因前後端由一人或一小組維護,溝通成本低
原因無須考慮服務分拆與整合問題

⚠️ 缺點與挑戰:

問題說明
容易變成單點瓶頸隨功能增長,Server 腫脹難維護
資料來源難擴充要串多系統(如 CRM、ERP)會變複雜
隊伍擴大時難協作所有人都改同一套 Schema 與 Resolver 容易衝突
說明隨功能增長,Server 腫脹難維護
說明要串多系統(如 CRM、ERP)會變複雜
說明所有人都改同一套 Schema 與 Resolver 容易衝突

模式二:GraphQL Gateway(Two Server Model)

GraphQL Gateway 是一種架構模式,專門用來整合多個子系統的資料來源。
它本身不執行業務邏輯,而是作為一層「查詢入口」,把前端送來的查詢拆解後,分別轉給對應的子服務。

📌 概念上,它就像是「API 的中控台」或「資料轉運站」。

📌 架構圖:

flowchart TD
    A[Client] -->|請求| B[GraphQL Gateway]
    B -->|查詢| C[User Service]
    B -->|查詢| D[Order Service]
    B -->|查詢| E[CMS Service]

    C -->|資料操作| F1[Database]
    C -->|資料操作| F2[REST API]
    C -->|資料操作| F3[GraphQL API]
    C -->|資料操作| F4[外部 API]

    D -->|資料操作| F1
    D -->|資料操作| F2
    D -->|資料操作| F3
    D -->|資料操作| F4

    E -->|資料操作| F1
    E -->|資料操作| F2
    E -->|資料操作| F3
    E -->|資料操作| F4

    F1 -->|SQL/NoSQL| G1[資料庫存儲]
    F2 -->|HTTP 請求| G2[RESTful 服務]
    F3 -->|查詢語言| G3[其他 GraphQL 服務]
    F4 -->|API 調用| G4[第三方服務]

每個子系統(Microservice)可以是:

  • REST API
  • GraphQL API
  • gRPC 服務
  • 甚至是第三方服務(如 Stripe、Firebase、Contentful)

特點:

面向說明
分工明確各服務各司其職,Gateway 專責整合
擴充彈性高可依功能獨立擴展服務,維護壓力分散
前端整合便利所有資料透過單一 Endpoint 查詢
可支援多端需求Mobile、Web、BI 可用同一 Gateway API
說明各服務各司其職,Gateway 專責整合
說明可依功能獨立擴展服務,維護壓力分散
說明所有資料透過單一 Endpoint 查詢
說明Mobile、Web、BI 可用同一 Gateway API

適用情境:

適用場景原因
微服務架構子系統獨立維運,需統一對外 API
多資料來源整合REST、GraphQL、DB 來源混合
跨團隊開發每組團隊可擁有自己子 GraphQL Schema
前後端解耦Gateway 負責 UI 所需欄位定義與轉換
原因子系統獨立維運,需統一對外 API
原因REST、GraphQL、DB 來源混合
原因每組團隊可擁有自己子 GraphQL Schema
原因Gateway 負責 UI 所需欄位定義與轉換

缺點與挑戰:

問題說明
Gateway 本身要設計得好查詢效率、錯誤傳遞、N+1 問題都需考量
需要協定管理多個服務 Schema 命名、版本、欄位定義需協調一致
初期建置較複雜Federation、Stitching 等技術學習門檻略高
說明查詢效率、錯誤傳遞、N+1 問題都需考量
說明多個服務 Schema 命名、版本、欄位定義需協調一致
說明Federation、Stitching 等技術學習門檻略高

結語:理解架構,才能靈活應用

如果說「Schema 是合約、Resolver 是執行人」,那整個 GraphQL Server 就是協助雙方溝通的舞台。

你現在已經了解:

  • Schema 與 Resolver 在伺服器中的角色
  • 手寫 vs 自動化 的開發方式
  • One Server vs Gateway 的部署差異
  • 為何要根據專案規模與資料來源選擇架構

這些認知將幫助你:

  • 正確設計查詢結構
  • 避免效能陷阱(如 N+1 問題)
  • 評估是否該引入 Hasura、Codegen 等工具
上一篇探索 .graphql 檔案:前端與後端如何分工協作?
下一篇GraphQL 初學者必讀:自動產生 Resolver 的工具與底層邏輯解析
目前還沒有留言,成為第一個留言的人吧!

發表留言

留言將在審核後顯示。

架構

目錄

  • GraphQL Server 的三大核心角色
  • 整體資料流程圖:Query 是怎麼跑的?
  • 詳細流程解析:GraphQL 查詢從送出到回傳的每一步
  • GraphQL Server 的開發方式:手寫 vs 自動化
  • 手寫型:Apollo Server + 自訂 Schema / Resolver
  • 自動化型:Hasura、PostGraphile 等工具
  • 小結:你該選哪一種?
  • GraphQL Server 在系統中的部署角色:單一伺服器 vs Gateway
  • 模式一:One Server Model(單一應用伺服器)
  • 模式二:GraphQL Gateway(Two Server Model)
  • 結語:理解架構,才能靈活應用