跳至主要內容

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 大幅提升了開發效率。

分享這篇文章