GraphQL 查詢語言 (GraphQL)
核心定義
GraphQL 是由 Facebook 提出的 API 查詢語言 + 執行環境。
核心理念
客戶端決定要什麼資料,伺服器只回應請求的欄位。
| 設計導向 | REST | GraphQL |
|---|---|---|
| 對象 | Resource | Query |
| URI | 多個資源 URI | 單一 /graphql |
| 決定方 | 伺服器定義欄位 | 客戶端決定欄位 |
四大核心特徵
1. 單一端點(Single Endpoint)
REST: GET /users/123
GET /users/123/orders
GET /users/123/profile
GraphQL: POST /graphql (所有操作都走這一個)
2. 精準查詢(Client-driven query)
客戶端只拿需要的欄位 → 解決 REST 的 over-fetching / under-fetching。
3. 強型別 Schema(Strongly Typed)
type User {
id: ID!
name: String!
email: String
orders: [Order!]!
}
type Order {
id: ID!
total: Float!
}
type Query {
user(id: ID!): User
}
- 自帶文件(GraphiQL / Playground 可探索 schema)
- 自動型別檢查
- Client SDK 可從 schema 生成
4. 單一請求跨多資源(Nested Queries)
一次查詢「使用者 + 訂單 + 通知」,REST 需要多次 call。
三種操作類型
| 操作 | 對應 HTTP 動詞類比 | 用途 |
|---|---|---|
| Query | GET | 讀取資料 |
| Mutation | POST/PUT/DELETE | 修改資料(新增、更新、刪除) |
| Subscription | 長連線推送 | 即時資料推送(類似 WS/SSE) |
Query 範例
query {
user(id: 123) {
name
email
orders {
id
total
}
}
}
回應精確對應:
{
"data": {
"user": {
"name": "Bohr",
"email": "bohr@example.com",
"orders": [
{ "id": "101", "total": 250 },
{ "id": "102", "total": 100 }
]
}
}
}
Mutation 範例
mutation {
createUser(name: "Bohr", email: "bohr@example.com") {
id
name
}
}
Subscription 範例
subscription {
newOrder {
id
total
}
}
底層通常走 WebSocket。
優點
| 優點 | 說明 |
|---|---|
| 精準取數 | 客戶端控制欄位 → 避免 over/under-fetching |
| 強型別 | Schema 即文件,前後端契約清楚 |
| 一次取多資源 | 減少 API call 次數,降低延遲 |
| 彈性演進 | 欄位可標 @deprecated,新舊客戶端共存 |
缺點與對策
1. 伺服器實作複雜
- 需維護完整的強型別 schema,前後端一致
- 每個 field 都需要 resolver
- Query 是 client-driven → 可能產生惡意或複雜查詢
對策:Query complexity / depth limit、rate limiting、persisted queries
2. N+1 問題 ⚠️
N+1 的成因
因為 resolver 是 field-based,巢狀查詢會導致:
1 次查詢:SELECT * FROM users LIMIT 10
N 次查詢:SELECT * FROM orders WHERE user_id = ? (每個 user 跑一次)
總計:1 + N 次資料庫查詢
對策:
| 方案 | 原理 |
|---|---|
| DataLoader | 批次合併同一 tick 內的查詢成 IN (...) 查詢 |
| Resolver 層 join | 直接在 resolver 做 JOIN / aggregation |
| Persisted queries | 只允許白名單 query,可預先最佳化 |
3. Cache 機制弱
REST 靠 URL 當 cache key(CDN/瀏覽器),GraphQL 所有查詢都 POST 到同一個 /graphql,body 不同回應不同 → 無法直接用 HTTP cache。
對策:
- Persisted queries:用 query hash 當 key,可走 GET + CDN
- Apollo / Relay client cache:normalized cache by entity ID
- @defer / @stream:漸進式載入
REST vs GraphQL 決策
| 場景 | 選 REST | 選 GraphQL |
|---|---|---|
| 對外公開 API、需廣泛相容 | ✅ | |
| 依賴瀏覽器/CDN HTTP caching | ✅ | |
| 簡單 CRUD | ✅ | |
| 行動裝置 / 弱網路 / 節省流量 | ✅ | |
| 前端畫面複雜、需組合多資源 | ✅ | |
| 前端需求常變、後端不想改 schema | ✅ | |
| 多團隊對重疊資料做廣泛查詢 | ✅ |
面試中如何談 GraphQL
- 定位:前端需要靈活查詢或一次取多種資源時用
- 典型案例:
- 行動裝置 / 弱網路 → 只抓需要欄位
- 複雜 Dashboard → 一次查詢解決
- 比較 REST:
- REST → 資源固定、快取好、簡單直觀
- GraphQL → 查詢彈性,但後端要處理查詢成本與 cache
- 設計考量:
- 資料結構複雜 + 前端需求常變 → GraphQL 有優勢
- 簡單 CRUD 或高效快取場景 → REST 更直接
面試建議
在固定需求的面試題中,GraphQL 的優勢較不明顯。
只在問題明顯聚焦於查詢彈性 / over-fetching 時才提出 GraphQL。
Simplification-with-exceptions
邊界情況
- GraphQL 不是只能配 HTTP:Subscription 通常走 WebSocket
- GraphQL 也能 GET:query 短時可走
GET /graphql?query=...,支援 CDN cache - 不一定比 REST 快:後端若沒優化 resolver,可能比 REST 慢很多
- 不適合檔案上傳:multipart 需額外規範(graphql-multipart-request-spec)
Related Notes
- 03-API-Design/01-API-Design-Framework — Over/under-fetching 是 concern 時選 GraphQL
- 03-API-Design/02-REST — GraphQL 解決 REST 的痛點
- 03-API-Design/04-RPC-and-gRPC — 內部服務的另一選擇
- 03-API-Design/05-API-Security — query complexity limit 防惡意查詢
- 03-API-Design/Practice-API-Design