GraphQL vs REST API:如何選擇合適的 API 架構
GraphQL vs REST API:如何選擇合適的 API 架構
在設計後端 API 時,GraphQL 和 REST 是最常見的兩種選擇。REST 歷經多年考驗,生態完整;GraphQL 由 Facebook 開發,解決了 REST 的一些固有痛點。但這不是誰好誰壞的問題,而是在不同情境下如何選擇的問題。
REST API 基礎
REST(Representational State Transfer)基於 HTTP 動詞和資源 URL:
GET /api/users # 取得所有使用者
GET /api/users/123 # 取得特定使用者
POST /api/users # 建立使用者
PUT /api/users/123 # 更新使用者
DELETE /api/users/123 # 刪除使用者// Express.js REST API 範例
import express from 'express';
const router = express.Router();
router.get('/users/:id', async (req, res) => {
const user = await db.user.findUnique({
where: { id: req.params.id },
include: { posts: true, followers: true },
});
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
router.post('/users', async (req, res) => {
const { name, email } = req.body;
const user = await db.user.create({ data: { name, email } });
res.status(201).json(user);
});GraphQL 基礎
GraphQL 讓客戶端精確指定需要的資料:
# Schema 定義
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
followers: [User!]!
followerCount: Int!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String): User!
}# 客戶端查詢:只要我需要的欄位
query GetUserProfile($userId: ID!) {
user(id: $userId) {
name
email
posts {
title
createdAt
}
followerCount
}
}// Apollo Server 實作
import { ApolloServer } from '@apollo/server';
const resolvers = {
Query: {
user: async (_, { id }) => {
return db.user.findUnique({ where: { id } });
},
},
User: {
posts: async (user) => {
return db.post.findMany({ where: { authorId: user.id } });
},
followerCount: async (user) => {
return db.follow.count({ where: { followingId: user.id } });
},
},
};REST 的痛點
Over-fetching(過度取得)
// 我只需要使用者的名字,但 REST 給我了所有東西
GET /api/users/123
// 回傳:{ id, name, email, bio, avatar, createdAt, updatedAt,
// lastLoginAt, settings, preferences, ... }Under-fetching 與 N+1 問題
// 要取得使用者列表及每人的最新文章
GET /api/users // 1 次請求,取得 20 個使用者
GET /api/users/1/posts // +1 次
GET /api/users/2/posts // +1 次
// ... 共 21 次請求!GraphQL 的解決方案
# 一次請求,精確取得需要的資料
query {
users(limit: 20) {
name # 只要名字
latestPost { # 關聯資料一次取得
title
}
}
}GraphQL 的挑戰
N+1 問題(DataLoader 解決)
import DataLoader from 'dataloader';
const postLoader = new DataLoader(async (userIds: readonly string[]) => {
// 批次查詢,一次取得所有使用者的文章
const posts = await db.post.findMany({
where: { authorId: { in: userIds as string[] } },
});
return userIds.map(id => posts.filter(p => p.authorId === id));
});
// 在 resolver 中使用
const resolvers = {
User: {
posts: (user, _, { loaders }) => loaders.posts.load(user.id),
},
};快取複雜性
REST 可以用 HTTP 快取(CDN、瀏覽器快取),GraphQL 因為都走 POST 請求,快取較複雜:
// Apollo Client 的快取設定
const client = new ApolloClient({
cache: new InMemoryCache({
typePolicies: {
User: {
fields: {
posts: {
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
}),
});功能對比
| 特性 | REST | GraphQL |
|---|---|---|
| 學習曲線 | 低 | 中高 |
| 過度取得 | 常見問題 | 不存在 |
| 不足取得 | 需多次請求 | 單次請求 |
| HTTP 快取 | 原生支援 | 需額外處理 |
| 類型系統 | 無(需 OpenAPI) | 內建 |
| 自動文件 | 需 OpenAPI/Swagger | 內建(Introspection) |
| 版本管理 | /v1/, /v2/ | 欄位棄用 |
| 效能調校 | 簡單 | 需要 DataLoader |
| 工具生態 | 極豐富 | 豐富 |
選擇建議
選 REST 的情況
// 簡單的 CRUD API
// 團隊已熟悉 REST
// 需要簡單的 HTTP 快取
// 公開 API(GraphQL 的 introspection 可能暴露過多資訊)
// 微服務間通訊選 GraphQL 的情況
// 複雜的資料圖(多個實體間有複雜關聯)
// 多種客戶端(Web、iOS、Android 各需不同資料)
// 快速迭代的前端(後端 schema 穩定,前端自由調整查詢)
// 資料聚合層(aggregating multiple REST APIs)混合架構
實際上很多系統兩者並用:
// GraphQL:複雜的資料查詢
// REST:檔案上傳、Webhook、簡單操作
app.use('/graphql', graphqlHandler);
app.post('/api/upload', uploadHandler);
app.post('/webhooks/payment', paymentWebhook);總結
REST 和 GraphQL 各有其適用場景。REST 簡單、成熟、工具豐富,適合大多數 API;GraphQL 靈活、強型別,適合前端需求多變、資料關聯複雜的場景。重要的是根據你的實際需求做選擇,而不是追隨潮流。許多成功的產品只用 REST,也有許多用 GraphQL 大幅提升了開發效率。
分享這篇文章