測驗:API 設計與資料儲存 — REST、GraphQL 與資料庫選型
共 5 題,點選答案後會立即顯示結果
1. 為什麼 Client 不直接存取資料庫,而是透過 API 溝通?
2. 在 REST API 中,如果要「建立一個新使用者」,應該使用哪個 HTTP 方法搭配哪個 URL?
3. REST API 的「Stateless(無狀態)」特性意味著什麼?
4. GraphQL 主要解決了 REST 的哪兩個問題?
5. 你正在開發一個電商系統,需要處理訂單和庫存,而且「扣款和出貨」必須綁在一起(全成功或全失敗)。根據文章的決策框架,你應該選擇哪種資料庫?為什麼?
你知道 client 和 server 會互相溝通,但「API」到底是什麼角色?REST 和 GraphQL 差在哪?資料庫為什麼有 SQL 和 NoSQL 兩大陣營?這篇文章幫你建立判斷框架。
這篇文章解決什麼問題?
如果你有以下困惑,這篇文章適合你:
- Client 為什麼不直接存取資料庫,中間那層 API 到底在幹嘛?
- REST 和 GraphQL 都是 API,到底什麼時候該用哪個?
- 資料庫選型時,SQL 和 NoSQL 怎麼選?
前置知識
這篇假設你已經讀過第 1 篇,理解 Client-Server 架構和 HTTP 基本概念。
1. API — Client 和 Server 之間的「中間人」
一句話說明
API(Application Programming Interface)是 client 和 server 之間的溝通合約,定義了「你可以問什麼、怎麼問、會得到什麼回答」。
為什麼不讓 client 直接存取資料庫?
想像一個場景:你在手機 App 上查自己的訂單。
直接存取資料庫(危險!不會這樣做):
App → 直接連 Database → SELECT * FROM orders WHERE user_id = 123
透過 API(正確做法):
App → 發 HTTP 請求給 API → API 驗證身份 → API 查資料庫 → 回傳結果
為什麼要多這一層?三個關鍵原因:
| 原因 | 解釋 |
|---|---|
| 安全性 | API 可以驗證身份、過濾請求。直接連資料庫等於把鑰匙交出去 |
| 解耦 | 前端和後端可以獨立開發。只要 API 合約不變,雙方隨便改 |
| 控制 | API 可以限制流量、記錄操作、轉換資料格式 |
最小範例:API 的 Request-Response 模式
Client 發出請求(Request):
GET /api/orders/456
Server 回傳回應(Response):
{
"id": 456,
"status": "shipped",
"total": 1200
}
Code language: JavaScript (javascript)翻譯:Client 問「456 號訂單的狀況如何?」,Server 回答「已出貨,總共 1200 元」。
每一次 API 溝通都遵循這個模式:Request(問)→ Response(答)。
2. REST API — 目前最主流的 API 風格
一句話說明
REST 用 URL 代表「資源」,用 HTTP 方法表示「要對資源做什麼」。
核心概念翻譯
| 你會看到 | 意思 |
|---|---|
GET /users/123 |
讀取 ID 為 123 的使用者 |
POST /users |
建立一個新使用者 |
PUT /users/123 |
更新 ID 為 123 的使用者(整個替換) |
DELETE /users/123 |
刪除 ID 為 123 的使用者 |
Stateless |
每個請求獨立,server 不記得上一個請求 |
逐行翻譯:一個典型的 REST API 呼叫
# 建立一個新使用者
curl -X POST https://api.example.com/users \ # 對 /users 發出 POST 請求(建立)
-H "Content-Type: application/json" \ # 告訴 server:我送的是 JSON 格式
-H "Authorization: Bearer eyJhb..." \ # 附上身份驗證 token
-d '{"name": "Alice", "email": "[email protected]"}' # 要建立的資料
Code language: PHP (php)Server 回應:
{
"id": 789,
"name": "Alice",
"email": "[email protected]",
"created_at": "2026-02-23T10:00:00Z"
}
Code language: JSON / JSON with Comments (json)翻譯:「我要在 /users 這個集合裡建立一筆新資料,資料內容是 name 和 email,server 回傳建立好的完整資料(包含自動產生的 id 和時間)」。
REST 的設計哲學:Resource-based
REST 的核心是「資源」。每個 URL 代表一個資源,HTTP 方法代表動作:
資源(名詞) + 動作(動詞) = API 端點
/users GET → 取得所有使用者
/users/123 GET → 取得特定使用者
/users POST → 建立新使用者
/users/123 PUT → 更新特定使用者
/users/123 DELETE → 刪除特定使用者
Stateless — 無狀態是什麼意思?
有狀態(Stateful)的對話:
你:「我要查訂單」
店員:(記住你是誰)「好的,王先生,你的訂單...」
無狀態(Stateless)的對話:
你:「我是王先生,這是我的會員卡,我要查訂單 456」
店員:「好的,456 號訂單已出貨」
你:「我是王先生,這是我的會員卡,我要查訂單 789」
店員:「好的,789 號訂單處理中」
每次請求都要「重新自我介紹」(帶上 token),server 不會記住你上次問了什麼。這看起來多餘,但好處是:任何一台 server 都能處理你的請求,不需要找到「上次跟你聊過的那台」,這對大規模系統很重要。
3. GraphQL — 讓 Client 決定要什麼資料
一句話說明
GraphQL 讓 client 精確描述「我要哪些欄位」,一次請求拿到所有需要的資料。
REST 的痛點:Over-fetching 與 Under-fetching
想像你在做一個「使用者個人頁面」,需要顯示使用者名稱和他的訂單列表。
用 REST,你可能需要兩個請求:
1. GET /users/123
回傳:{ id, name, email, phone, address, birthday, ... }
→ Over-fetching:你只需要 name,卻拿到一堆不需要的欄位
2. GET /users/123/orders
回傳:[{ id, status, total, items, ... }, ...]
→ 要發第二個請求才能拿到訂單
→ Under-fetching:一個請求拿不到你要的所有資料
GraphQL 怎麼解決
# 用 GraphQL,一個請求搞定,而且只拿你要的欄位:
query {
user(id: 123) { # 我要 ID 123 的使用者
name # 只要名字
orders { # 還有他的訂單
id # 訂單只要 id
status # 和狀態
}
}
}
Code language: PHP (php)Server 回應:
{
"data": {
"user": {
"name": "Alice",
"orders": [
{ "id": 456, "status": "shipped" },
{ "id": 789, "status": "pending" }
]
}
}
}
Code language: JSON / JSON with Comments (json)翻譯:「給我 123 號使用者的名字,還有他所有訂單的 id 和狀態,其他的我不要」。
REST vs GraphQL 快速決策樹
Q1: 你的前端需要的資料格式是否經常變動?
├─ 經常變(快速迭代中)→ 考慮 GraphQL
└─ 很穩定 → 繼續 Q2
Q2: 前端需要的資料是否需要多次 API 呼叫才能湊齊?
├─ 是(要打好幾個 endpoint)→ 考慮 GraphQL
└─ 一個 endpoint 就能搞定 → 繼續 Q3
Q3: 你的團隊有 GraphQL 經驗嗎?
├─ 有 → 用 GraphQL
└─ 沒有 → 用 REST(學習成本低,生態成熟)
情境速查表
| 情境 | 建議 | 原因 |
|---|---|---|
| 簡單 CRUD 應用 | REST | 直覺、工具成熟、夠用 |
| 手機 App(省流量) | GraphQL | 只拿需要的欄位,省頻寬 |
| 多種 client 共用同一 API | GraphQL | 各 client 自訂查詢 |
| 第三方開放 API | REST | 易理解、文件好寫 |
| 微服務之間的溝通 | REST(或 gRPC) | 簡單直接,效能好 |
| 複雜的巢狀資料查詢 | GraphQL | 一次查詢取得所有關聯資料 |
常見坑:GraphQL 不是萬靈丹
症狀:聽說 GraphQL 比 REST 好,全面改用 GraphQL,結果開發變慢。
為什麼會發生: GraphQL 需要額外的學習成本(Schema 定義、Resolver 撰寫、Client 端 cache 管理)。對於簡單的 CRUD 應用,REST 的 convention 已經夠用,引入 GraphQL 反而增加複雜度。
判斷原則: 如果你的 API 用 REST 寫起來很順,沒有遇到 over-fetching 或 under-fetching 的痛點,就不需要切換到 GraphQL。解決真實存在的問題,不要解決假想的問題。
4. Database — 為什麼不直接用檔案存資料?
一句話說明
資料庫是專門用來高效儲存、查詢、管理大量資料的系統。
檔案 vs 資料庫
用檔案存資料:
- 想找一筆訂單? → 讀取整個檔案,一行行找
- 兩個人同時改資料? → 檔案可能被覆蓋
- 想保證「扣款和出貨」一起完成? → 自己寫邏輯,超容易出 bug
用資料庫:
- 想找一筆訂單? → 用索引(Index),毫秒級找到
- 兩個人同時改資料? → 資料庫自動處理並發(Concurrency Control)
- 想保證「扣款和出貨」一起完成? → 交易(Transaction),全成功或全失敗
資料庫提供的三大保障
| 保障 | 解釋 | 類比 |
|---|---|---|
| 索引(Index) | 幫資料建目錄,加速查詢 | 書本的索引頁,不用翻完整本書 |
| 交易(Transaction) | 多個操作綁在一起,全成功或全失敗 | 銀行轉帳:扣款和入帳必須同時完成 |
| 並發控制(Concurrency) | 多人同時操作不會互相干擾 | 多人同時編輯 Google Docs 不會覆蓋彼此 |
5. SQL vs NoSQL — 資料庫怎麼選?
一句話說明
SQL 是「結構明確的表格」,NoSQL 是「彈性的資料容器」。
最小範例對照
SQL(以 PostgreSQL 為例):
-- 資料長什麼樣(Schema),必須事先定義
CREATE TABLE users (
id INT PRIMARY KEY, -- 每個使用者有唯一 ID
name VARCHAR(100), -- 名字,最多 100 字
email VARCHAR(200) -- 信箱,最多 200 字
);
-- 查詢:找出所有訂單金額超過 1000 的使用者
SELECT users.name, orders.total
FROM users
JOIN orders ON users.id = orders.user_id -- 把兩張表連起來
WHERE orders.total > 1000; -- 條件:金額超過 1000
翻譯:先定義「使用者長什麼樣」(像 Excel 的欄位標題),然後用 SQL 語言跨表格查詢。
NoSQL(以 MongoDB 為例):
// 直接塞資料,不用事先定義 Schema
db.users.insertOne({
name: "Alice",
email: "[email protected]",
orders: [ // 訂單直接塞在使用者裡面
{ id: 1, total: 1500 },
{ id: 2, total: 800 }
]
})
// 查詢:找出有訂單金額超過 1000 的使用者
db.users.find({ "orders.total": { $gt: 1000 } })
Code language: PHP (php)翻譯:資料像 JSON 一樣塞進去,不用事先定義格式。相關的資料(訂單)直接放在同一個文件裡。
核心差異比較
| 面向 | SQL(關聯式) | NoSQL(非關聯式) |
|---|---|---|
| 資料結構 | 固定欄位(表格) | 彈性結構(JSON-like) |
| 關聯性 | 用 JOIN 連接多張表 | 通常把相關資料放在同一份文件 |
| Schema | 必須事先定義 | 隨時可以新增欄位 |
| 一致性保證 | ACID(強一致性) | 多數是最終一致性 |
| 擴展方式 | 垂直擴展(升級機器) | 水平擴展(加更多機器) |
| 查詢語言 | 標準 SQL | 各家不同(MongoDB 用 JSON 語法) |
ACID 是什麼?一張圖看懂
ACID 就是資料庫的四個保證:
A - Atomicity(原子性)
「全做或全不做」
例:轉帳時,扣款和入帳要麼都成功,要麼都不做
C - Consistency(一致性)
「資料永遠合理」
例:帳戶餘額不會變成負數
I - Isolation(隔離性)
「互不干擾」
例:兩個人同時轉帳,結果和一個一個轉一樣
D - Durability(持久性)
「存了就不會丟」
例:系統當機重啟後,資料還在
SQL vs NoSQL 快速決策樹
Q1: 你的資料之間有明確的關聯嗎?(例如:使用者有訂單,訂單有商品)
├─ 有,而且經常需要跨表查詢 → SQL
└─ 沒有太多關聯,或可以反正規化 → 繼續 Q2
Q2: 資料格式會經常改變嗎?
├─ 會(快速迭代、每個物件欄位不同)→ NoSQL
└─ 不太會 → 繼續 Q3
Q3: 你需要強一致性(ACID)嗎?
├─ 需要(金融、庫存等不能出錯的)→ SQL
└─ 可以接受短暫的不一致(社群貼文、日誌等)→ NoSQL
Q4: 你預期要處理多大量的資料?
├─ 超大量 + 需要水平擴展 → NoSQL
└─ 中等規模 → SQL(PostgreSQL 已經很能打了)
情境速查表
| 情境 | 建議 | 原因 |
|---|---|---|
| 電商系統(訂單、庫存) | SQL(PostgreSQL) | 需要 ACID、複雜 JOIN 查詢 |
| 社群平台的動態牆 | NoSQL(MongoDB) | 資料格式多變、讀取量大 |
| 使用者認證系統 | SQL | 帳號資料需要強一致性 |
| IoT 感測器資料 | NoSQL / 時序資料庫 | 大量寫入、結構簡單 |
| 快取層 | Redis(NoSQL) | 毫秒級讀寫、key-value 結構 |
| 新創 MVP 快速開發 | SQL(PostgreSQL) | 萬用、社群大、遇到問題好找解答 |
常見坑:NoSQL 不等於不需要設計
症狀:選了 MongoDB,以為不用管 Schema,結果資料越來越亂,查詢越來越慢。
為什麼會發生: NoSQL 的「彈性 Schema」不代表「不需要設計」。你還是需要根據查詢模式來設計資料結構。例如,MongoDB 的效能高度依賴你怎麼組織文件(Document)— 哪些資料要嵌入(embed),哪些要引用(reference)。
怎麼避免: 先想清楚「你的資料會怎麼被查詢」,再決定資料怎麼存。跟 AI 說:
「我的應用有使用者和訂單兩種資料,主要的查詢場景是 [列出場景]。我用 MongoDB,幫我設計 Document 結構,並解釋為什麼這樣設計。」
完整對照:一個請求從 Client 到 Database
把這篇講的所有概念串起來:
1. 使用者在手機 App 按下「查看訂單」
2. App(Client)發出 REST API 請求:
GET /api/orders?user_id=123
Authorization: Bearer eyJhb...
3. Server 收到請求,API 層處理:
- 驗證 token(這人是誰?有權限嗎?)
- 解析參數(要查 user_id=123 的訂單)
4. Server 查詢資料庫:
SELECT * FROM orders WHERE user_id = 123;
5. 資料庫回傳結果,Server 組裝回應:
{ "orders": [{ "id": 456, "status": "shipped" }] }
6. App 收到回應,顯示在畫面上
Code language: JavaScript (javascript)小結
記住這個判斷框架:
- API 是必要的中間層 — 安全性、解耦、控制,這三個理由讓 API 成為標準做法
- REST vs GraphQL — 大部分情境 REST 就夠了。遇到 over-fetching / under-fetching 的痛點再考慮 GraphQL
- SQL vs NoSQL — 資料關聯明確 + 需要強一致性 = SQL;資料格式多變 + 需要水平擴展 = NoSQL
最重要的原則: 選技術要看你的實際需求,不要因為某個技術「比較新」或「大公司在用」就選它。大部分情況下,PostgreSQL + REST API 就能解決 90% 的問題。
當你不確定時,跟 AI 說:
「我在做一個 [應用類型],資料有 [描述資料關聯],預期規模是 [使用者數 / 資料量]。REST 和 GraphQL 哪個適合?資料庫該選 SQL 還是 NoSQL?幫我分析優缺點。」
下一篇預告
第 3 篇我們會進入「API 架構模式」,討論 Proxy、Load Balancer,以及 API Gateway — 當你的系統不只一台 server 時,要怎麼管理流量和路由。
進階測驗:API 設計與資料儲存
共 5 題,包含情境題與錯誤診斷題。
1. 手機 App 的 API 選型 情境題
作為技術決策者,你會建議哪個方案?
2. 新創 MVP 的資料庫選型 情境題
你會怎麼回應這個建議?
3. API 中間層的必要性 情境題
你會怎麼說服同事加上 API 層?
4. REST API 設計錯誤 錯誤診斷
這組 API 設計的核心問題是什麼?
5. MongoDB 效能問題診斷 錯誤診斷
這個設計的根本問題是什麼?