跳至主要內容
Skip to content

GraphQL vs REST:取捨與選擇

GraphQL 和 REST 是兩種主流的 API 設計方式。本篇將深入比較,幫助你做出正確的選擇。


一、 基本概念

1.1 REST

REST(Representational State Transfer)是以資源為中心的架構風格:

bash
GET /users/1
GET /users/1/posts
GET /posts/1/comments

1.2 GraphQL

GraphQL 是一種查詢語言,讓前端決定要什麼資料:

graphql
query {
  user(id: 1) {
    name
    posts {
      title
      comments {
        content
      }
    }
  }
}

二、 核心差異

2.1 請求方式

2.2 取得資料

問題RESTGraphQL
Over-fetching返回過多資料只拿需要的
Under-fetching需要多次請求一次取得
N+1 問題容易發生需要特別處理

2.3 範例對比

javascript
// REST:需要 3 個請求
const user = await fetch("/users/1").then((r) => r.json());
const posts = await fetch(`/users/1/posts`).then((r) => r.json());
const comments = await Promise.all(
  posts.map((p) => fetch(`/posts/${p.id}/comments`).then((r) => r.json()))
);

// GraphQL:1 個請求
const { data } = await fetch("/graphql", {
  method: "POST",
  body: JSON.stringify({
    query: `
      query {
        user(id: 1) {
          name
          posts {
            title
            comments { content }
          }
        }
      }
    `,
  }),
}).then((r) => r.json());

三、 GraphQL 深入

3.1 Schema 定義

graphql
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
}

type Comment {
  id: ID!
  content: String!
  author: User!
}

type Query {
  user(id: ID!): User
  users: [User!]!
  post(id: ID!): Post
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

input CreateUserInput {
  name: String!
  email: String!
}

3.2 Express + Apollo 實作

javascript
const { ApolloServer } = require("@apollo/server");
const { expressMiddleware } = require("@apollo/server/express4");

const typeDefs = `
  type User {
    id: ID!
    name: String!
    posts: [Post!]!
  }
  
  type Post {
    id: ID!
    title: String!
  }
  
  type Query {
    user(id: ID!): User
    users: [User!]!
  }
`;

const resolvers = {
  Query: {
    user: (_, { id }) => User.findById(id),
    users: () => User.find(),
  },
  User: {
    posts: (user) => Post.find({ authorId: user.id }),
  },
};

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

app.use("/graphql", expressMiddleware(server));

3.3 N+1 問題解決

javascript
const DataLoader = require("dataloader");

// 批次載入
const postLoader = new DataLoader(async (userIds) => {
  const posts = await Post.find({ authorId: { $in: userIds } });

  // 按用戶分組
  const postsByUser = posts.reduce((acc, post) => {
    acc[post.authorId] = acc[post.authorId] || [];
    acc[post.authorId].push(post);
    return acc;
  }, {});

  return userIds.map((id) => postsByUser[id] || []);
});

const resolvers = {
  User: {
    posts: (user) => postLoader.load(user.id),
  },
};

四、 優缺點比較

4.1 REST 優點

優點說明
簡單易懂HTTP 方法直觀
快取友好可用 HTTP 快取
工具成熟cURL、Postman 等
無需學習標準 HTTP
易於除錯每個端點獨立

4.2 REST 缺點

缺點說明
Over-fetching返回不需要的資料
Under-fetching需要多次請求
版本管理需要明確版本策略
文檔維護需要額外工具

4.3 GraphQL 優點

優點說明
精確取得只拿需要的資料
單一端點簡化請求
強型別Schema 即文檔
自省能力自動生成文檔
版本無感新增欄位不破壞

4.4 GraphQL 缺點

缺點說明
學習曲線新概念較多
快取困難POST 請求難快取
N+1 問題需要 DataLoader
複雜查詢可能造成性能問題
檔案上傳較為複雜

五、 何時選擇哪個?

5.1 選擇 REST

markdown
✅ 簡單的 CRUD 應用
✅ 需要 HTTP 快取
✅ 團隊熟悉 REST
✅ 公開 API
✅ 資源模型簡單

5.2 選擇 GraphQL

markdown
✅ 複雜的關聯資料
✅ 多平台(Web、行動)
✅ 頻繁變更的需求
✅ 前端主導的團隊
✅ 需要即時訂閱

5.3 決策樹


六、 混合使用

6.1 REST + GraphQL

javascript
// REST 用於簡單端點
app.get("/health", (req, res) => res.json({ status: "ok" }));
app.post("/auth/login", loginHandler);
app.post("/upload", uploadHandler);

// GraphQL 用於複雜查詢
app.use("/graphql", graphqlMiddleware);

6.2 BFF 模式


七、 效能考量

7.1 GraphQL 查詢複雜度限制

javascript
const { createComplexityLimitRule } = require("graphql-validation-complexity");

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    createComplexityLimitRule(1000), // 限制複雜度
  ],
});

7.2 深度限制

javascript
const depthLimit = require("graphql-depth-limit");

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(5), // 最多 5 層嵌套
  ],
});

7.3 持久化查詢

javascript
// 預先註冊查詢
const queries = {
  'getUserWithPosts': `
    query GetUser($id: ID!) {
      user(id: $id) {
        name
        posts { title }
      }
    }
  `
}

// 請求只需發送 ID
POST /graphql
{ "id": "getUserWithPosts", "variables": { "id": "1" } }

總結

面向RESTGraphQL
學習曲線
靈活性
快取
效能穩定取決於查詢
適用場景通用複雜關聯

> **沒有絕對的對錯**——根據專案需求、團隊能力、時間限制來選擇。


進階挑戰

  1. 將一個 REST API 遷移到 GraphQL,比較開發體驗。
  2. 實作 GraphQL Subscription(即時訂閱)。
  3. 研究 tRPC,了解另一種類型安全的 API 方式。

延伸閱讀與資源