
系列文章:DOM 與架構 → 非同步、CSS 與狀態管理 → 本篇:API 設計與部署
API 設計的演進:SOAP → REST → GraphQL → tRPC
SOAP:企業級的沉重
Web 2.0 之前,系統間通訊靠 SOAP。它一點都不 Simple:
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUser xmlns="http://example.com/users">
<UserId>123</UserId>
</GetUser>
</soap:Body>
</soap:Envelope>同樣的操作用 REST:GET /api/users/123。你就知道為什麼 SOAP 被淘汰了。
REST:簡潔的革命
用 HTTP 動詞表達操作、URL 路徑表達資源、JSON 作為資料格式。跟 HTTP 天然契合,不需要額外協議。REST 統治 Web API 十多年,至今仍是最被廣泛使用的風格。
但隨著 SPA 和行動裝置普及,痛點越來越明顯:
GET /api/users/123 → 回傳所有欄位(你只需要名字和頭像)
GET /api/users/123/posts → 回傳所有文章(你只需要最新 5 篇標題)
GET /api/users/123/followers → 回傳所有追蹤者(你只需要數量)
Over-fetching:每個 endpoint 回傳的資料比你需要的多。Under-fetching:一個 endpoint 不夠,要打三個才能拼出一個頁面。在弱網環境下,多餘的資料傳輸和多次網路往返就是效能殺手。
GraphQL:客戶端決定要什麼(2015)
query {
user(id: 123) {
name
avatar
posts(last: 5) { title }
followersCount
}
}一個請求、精確的資料、沒有多餘欄位。但也帶來新複雜性:Schema 維護成本、HTTP 快取困難(所有請求都打同一個 endpoint)、N+1 query 問題、學習曲線陡峭。
tRPC:TypeScript 全端的極致
如果前後端都用 TypeScript:
// 後端定義
const appRouter = router({
getUser: procedure
.input(z.object({ id: z.number() }))
.query(({ input }) => db.user.findUnique({ where: { id: input.id } })),
});
// 前端直接用——完整型別推導,改後端會立即在前端報錯
const user = trpc.getUser.useQuery({ id: 123 });完全跳過 Schema 定義,型別直接從後端推導到前端。但只適用於 TypeScript 全端專案——如果你有 iOS / Android 客戶端,tRPC 就不行了。
選擇指南:
- 大多數團隊 → REST(最務實)
- 資料需求複雜且變動頻繁 → GraphQL
- TypeScript 全端小團隊 → tRPC
沒有人應該選 SOAP
部署的演進:FTP → CI/CD → Docker → Serverless
FTP 時代:用勇氣部署
打開 FileZilla,把檔案拖到伺服器。沒有版本控制——改壞了就祈禱自己記得改了哪些。兩個人同時改同一個檔案?最後上傳的那個直接覆蓋前一個人的工作,沒有合併、沒有衝突提示。
回滾就是把「之前的檔案」再傳上去——前提是你還留著。沒留著?那就寫遺書吧。
CI/CD:讓機器來做
Git 解決了版本控制,CI/CD(Jenkins、GitHub Actions)解決了自動化部署:
on:
push:
branches: [main]
jobs:
deploy:
steps:
- run: npm test
- run: npm run build
- run: deploy-to-production推 code 後自動測試、自動建置、自動部署。整個流程標準化、可重複、有紀錄。消除了大部分「在我的電腦上可以跑」的問題——但還沒有完全解決,因為環境可能還是不一樣。
Docker:把環境打包帶走(2013)
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"]不只打包程式碼,連執行環境一起打包。在 Mac 上跑、在 CI 上跑、在雲端跑——結果都一模一樣。Docker 還催生了微服務架構,每個服務獨立打包、獨立部署、獨立擴展。
但你仍然要管伺服器——監控健康狀態、管理資源分配、處理安全更新。
Serverless:連伺服器都不管了
exports.handler = async (event) => {
const user = await db.getUser(event.pathParameters.id);
return { statusCode: 200, body: JSON.stringify(user) };
};有請求就自動處理,沒請求不計費。不用管 Nginx、不用擔心擴展。
限制也很明顯:冷啟動延遲、執行時間限制(通常 15 分鐘)、不適合有狀態的長連線、vendor lock-in。
整條演進鏈的方向一直很明確:減少人為操作,增加自動化和標準化。 從手動 FTP 到自動 pipeline,從手動配置環境到容器化,從管伺服器到不碰伺服器——每一步都在把開發者從基礎設施中解放出來。
系列總結:理解演進,才能做出選擇
回顧整個系列,有一個反覆出現的模式:
痛點 → 解決方案 → 新痛點 → 新解決方案
這不是退步,是在不同 trade-off 之間螺旋上升。理解這些「為什麼」,有三個實際好處:
做出更好的技術選型。 你會問:「這個工具解決的問題,是不是我真正面對的問題?」而不是被社群熱度左右。
更快學會新技術。 理解了 Callback → Promise → async/await 的脈絡,學任何語言的非同步處理都會很快。
預測未來方向。 看到一個技術的痛點被越來越多人討論,你可以推測下一代方案會往哪走。
不是每個新工具都適合你的場景。一個簡單的內容網站不需要 React,一個三人團隊的 MVP 不需要 Kubernetes。技術選型不是選最新的,是選最適合你當前問題的。
不要追逐潮流。理解 trade-off,然後做出清醒的選擇。