API 集成入门:REST vs GraphQL 对比与选型指南

无论你是前端开发者还是后端开发者,API 都是日常工作中不可或缺的部分。REST 多年来一直是 API 设计的标准,而 GraphQL 作为一种更灵活的新范式正在快速普及。

举个具体的例子:假设你在做一个内容管理后台,用户详情页要同时展示用户资料、他最近发布的 5 篇文章、每篇文章的前 3 条评论。用 REST 可能要发 7 次请求、再在前端手动拼装;用 GraphQL 一次查询就能拿到完整结构。这种“数据获取方式”的差异会直接体现在页面加载时间和前端代码复杂度上。下面先从两种范式的基础讲起。

一、REST 基础

1.1 什么是 REST

REST(Representational State Transfer)是一种基于 HTTP 协议的 API 设计风格。核心思想是将资源抽象为 URL,通过 HTTP 方法进行操作。

# RESTful API 设计
GET    /api/users          # 获取用户列表
GET    /api/users/1        # 获取单个用户
POST   /api/users          # 创建用户
PUT    /api/users/1        # 更新用户
DELETE /api/users/1        # 删除用户

1.2 REST 请求示例

// 获取用户及其文章
const user = await fetch('/api/users/1').then(r => r.json());
const posts = await fetch('/api/users/1/posts').then(r => r.json());
const comments = await fetch('/api/posts/1/comments').then(r => r.json());

// 响应通常包含固定结构
// GET /api/users/1
{
  "id": 1,
  "name": "张三",
  "email": "[email protected]",
  "avatar": "https://..."
}

1.3 REST 状态码与语义

REST 的规范程度体现在 HTTP 状态码上,语义清晰的接口应当善用它们:

状态码 含义 典型场景
200 OK 成功 查询返回资源
201 Created 创建成功 POST 新建后返回 Location
204 No Content 成功但无返回体 DELETE 删除成功
400 Bad Request 参数错误 校验失败
401 / 403 未认证 / 无权限 登录失效、越权访问
404 Not Found 资源不存在 无效 ID
429 Too Many Requests 触发限流 频控生效
500 / 502 / 503 服务端异常 代码错误、上游故障

很多团队的 REST 接口“长得很 REST”,却一律返回 200 加业务码,等于丢掉了 HTTP 自带的语义,也丢掉了中间层(CDN、网关、代理)基于状态码做缓存与重试的能力。

二、GraphQL 基础

2.1 什么是 GraphQL

GraphQL 是由 Facebook 开发的一种 API 查询语言。客户端可以精确指定需要的数据,不多不少。

# GraphQL 查询
query {
  user(id: 1) {
    name
    email
    posts {
      title
      comments {
        content
        author {
          name
        }
      }
    }
  }
}

2.2 GraphQL 响应

{
  "data": {
    "user": {
      "name": "张三",
      "email": "[email protected]",
      "posts": [
        {
          "title": "第一篇文章",
          "comments": [
            {
              "content": "写得好!",
              "author": {
                "name": "李四"
              }
            }
          ]
        }
      ]
    }
  }
}

2.3 变更(Mutation)与订阅

GraphQL 用 query 读数据,用 mutation 写数据,两者语法一致:

mutation {
  createPost(input: { title: "你好,GraphQL", authorId: 1 }) {
    id
    title
    createdAt
  }
}

subscription 用于推送类场景(实时评论、在线状态),这是 REST 需要自己用 WebSocket 约定的能力。需要提醒的是,GraphQL 的写入同样要做鉴权与参数校验——它解决的是“取数灵活”,不是“安全豁免”。

三、核心差异对比

3.1 数据获取

维度 REST GraphQL
数据量 固定结构,可能过多或不足 客户端精确指定
请求次数 可能需要多次请求 一次请求获取所有数据
嵌套数据 需多次请求或自定义端点 直接在查询中嵌套

3.2 示例对比

场景:获取用户信息 + 最新的 5 篇文章 + 每篇文章的前 3 条评论

REST 方式

// 3 次 API 请求
const user = await fetch('/api/users/1');
const posts = await fetch('/api/users/1/posts?limit=5');
const comments = await Promise.all(
  posts.map(p => fetch(`/api/posts/${p.id}/comments?limit=3`))
);
// 或者后端专门创建一个聚合端点

GraphQL 方式

query {
  user(id: 1) {
    name
    email
    posts(last: 5) {
      title
      comments(last: 3) {
        content
        author { name }
      }
    }
  }
}
// 一次请求获得所有数据

3.3 性能对比

特性 REST GraphQL
缓存 HTTP 缓存天然支持 需要额外缓存方案
网络请求数 可能较多 通常较少
载荷大小 可能包含不需要的数据 客户端精确指定
服务器性能 可控(预定义的数据集) 需防范复杂查询

3.4 缓存策略

REST 可以免费搭上 HTTP 缓存:给 GET 响应加 Cache-Control: max-age=3600ETagLast-Modified,CDN 和浏览器就能自动缓存,图片和静态接口的命中率很高。GraphQL 默认所有请求都走同一个 /graphql POST 端点,无法直接命中 HTTP 缓存,需要额外方案:Apollo 的自动持久化查询(APQ)把查询字符串换成哈希 ID,配合 CDN 缓存 GET 请求;服务端也可引入 DataLoader 做请求级去重与缓存。对以读为主的业务(商品详情、文章页),REST 在缓存上仍占明显优势。

3.5 版本管理

REST 常见 /api/v1/users/api/v2/users 这样的显式版本号,向后不兼容的改动靠升版本号隔离。GraphQL 没有 URL 版本概念,官方思路是增量演进:只加字段、不改语义,废弃字段用 @deprecated 标注并逐步下线。好处是不用维护多套端点;代价是团队要有良好的 schema 评审习惯,否则查询里塞满废弃字段,语义会越来越难维护。

四、服务端实现

REST 实现(Node.js + Express)

// REST 路由
const express = require('express');
const app = express();

app.get('/api/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);
  res.json(user);
});

app.get('/api/users/:id/posts', async (req, res) => {
  const posts = await db.findPostsByUser(req.params.id);
  res.json(posts);
});

GraphQL 实现(Node.js + Apollo Server)

const { ApolloServer, gql } = require('apollo-server');

// 类型定义
const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
  }

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

  type Query {
    user(id: ID!): User
  }
`;

// 解析器
const resolvers = {
  Query: {
    user: async (_, { id }) => db.findUser(id),
  },
  User: {
    posts: async (user) => db.findPostsByUser(user.id),
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen(4000);

五、选型建议

选 REST 的场景

  • 简单 CRUD 应用:数据模型简单,不需要嵌套查询
  • 缓存是关键需求:REST 的 HTTP 缓存机制成熟
  • 微服务架构:服务间 REST 接口更加直观
  • 文件上传/下载:处理二进制数据更自然
  • 团队对 REST 更熟悉:降低学习成本

选 GraphQL 的场景

  • 复杂的前端数据需求:不同页面需要不同数据集
  • 多个前端客户端:Web + Mobile 需要不同数据
  • API 需要快速迭代:前端需求变更时后端不需要改接口
  • 数据有复杂的嵌套关系:一次查询获取关联数据

一个真实的选型案例

朋友做的一个跨境电商后台,最初用 REST。随着运营要的报表维度越来越多,后端每隔几周就要加一个聚合端点,前端还要串好几次请求,维护成本直线上升。后来团队把“面向运营后台的查询”迁到 GraphQL,对外公开的订单、商品接口仍保留 REST,半年下来新增查询需求基本不用动后端。这个案例的结论很朴素:对外、重缓存的接口用 REST,对内、重取数的界面用 GraphQL,比“全站统一一种”更现实。

六、混合方案

很多团队采用 REST + GraphQL 混合方案:

方案一:GraphQL 作为 API Gateway
  GraphQL Gateway → REST 服务 A / REST 服务 B / REST 服务 C
  前端只与 GraphQL 交互,后端服务之间用 REST

方案二:一部分用 REST,一部分用 GraphQL
  公开 API → REST(缓存友好)
  内部前端 → GraphQL(灵活取数)

七、总结

REST 和 GraphQL 不是非此即彼的选择。REST 胜在简洁、缓存友好和生态成熟GraphQL 胜在灵活、高效和类型安全。对于新项目,可以考虑从 GraphQL 开始(特别是前后端分离的 SPA 应用);对于已有 REST API,可以逐步引入 GraphQL,没有迁移压力。技术选型的核心是匹配业务需求,而不是追逐热门技术。

常见误区

  • “GraphQL 一定更快”:不一定。GraphQL 把“少请求”做得好,但 resolver 写得不讲究时,一条深嵌套查询可能触发几十次数据库查询,比 REST 更慢,配合 DataLoader 和查询深度限制才是关键。
  • “REST 不能做复杂查询”:可以,用查询参数、过滤器和专用聚合端点都能实现,只是每次需求变化都要改后端。
  • “用了 GraphQL 就不用写文档”:恰恰相反,schema 就是文档,但要维护得好;GraphQL 的 introspection 能自动生成可交互文档(如 GraphiQL)。

参考:MDN 关于 HTTP 方法 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods ;GraphQL 官方文档 https://graphql.org/learn/ ;Apollo 持久化查询 https://www.apollographql.com/docs/apollo-server/performance/apq/