GraphQL — 是一种用于 API 的查询语言和执行这些查询的运行时环境,由 Facebook 于 2012 年开发,并于 2015 年开源。与 REST 不同,REST 中服务器决定响应的结构,而 GraphQL 允许客户端精确指定需要哪些数据,从而完全消除 overfetching 和 underfetching 的问题。根据 State of JavaScript Survey (2025),35% 的受访开发者使用 GraphQL,在大型公司中,GitHub、Shopify、Airbnb 和 The New York Times 已采用它。GraphQL 支持三种类型的操作:query(读取)、mutation(写入)和 subscription(通过 WebSocket 实现实时更新)。
要点
GraphQL — 是一种用于 API 的规范和运行时环境,让客户端完全控制接收到的数据。由 Facebook 的工程师为解决移动应用 News Feed 的问题而开发,该规范于 2015 年作为开放标准发布。自 2018 年起,GraphQL 由 GraphQL 基金会管理,并得到 Linux 基金会以及 Apollo、AWS、GitHub、SAP 等公司的支持。
与 REST 不同,REST 中每个端点返回固定的数据结构,而 GraphQL 使用一个接收查询字符串的单一端点。客户端在查询中描述需要哪些字段,服务器则精确返回这些字段。例如,查询 { user(id: “1”) { name email } } 将只返回用户的 name 和 email,而不会包含在 REST 中需获取的 address、phone 或 createdAt 等额外字段。
GraphQL 不绑定于任何特定的数据库或语言。规范仅定义了查询和响应的格式。存在 Node.js(graphql-js、Apollo Server)、Kotlin(graphql-kotlin、Netflix DGS Framework)、Python(Graphene、Strawberry)、Ruby(graphql-ruby)等语言的服务器实现。客户端库可用于所有主流平台,包括适用于 iOS、Android 和 Web 的 Apollo Client。
GraphQL 架构由三个关键组件组成:架构(Schema)、解析器(Resolvers)和执行引擎(GraphQL Engine)。架构确定哪些数据类型可用、可以执行哪些查询以及它们接受哪些参数。解析器是服务器上为架构的每个字段返回数据的函数。执行引擎接收传入的查询,根据架构进行验证,调用相应的解析器并构建响应。
查询的处理过程如下:
GraphQL 架构的主要优势是 字段级解析。在 REST 中,开发者要么获取资源的所有字段(可能包含多余数据),要么使用 ?fields=name,email 等扩展。在 GraphQL 中,这种过滤已内置于语言中:每个查询明确指定需要哪些字段,服务器精确返回这些字段。这对移动应用尤为重要,因为传输的数据量直接影响加载速度和流量消耗。
GraphQL 定义了三种类型的操作,每种操作对应特定的交互场景。Query — 用于读取数据,类似于 REST 中的 GET。Mutation — 用于修改数据(创建、更新、删除),类似于 POST/PUT/DELETE。Subscription — 用于通过 WebSocket 进行实时更新,在经典 REST 中没有直接对应项(需要 WebSocket 或 Server-Sent Events 等额外解决方案)。
查询的基本语法直观易懂:
// 带参数的简单查询
query {
user(id: "42") {
name
email
avatarUrl
}
}
// 返回已修改数据的变异
mutation {
updateProfile(name: "伊万") {
id
name
updatedAt
}
}
// Subscription — 监听实时更新
subscription {
newMessage(chatId: "chat_1") {
id
text
sender { name }
}
}
Query 并行执行 — 同一级别的所有字段同时加载。这允许通过一个查询加载相关数据(用户及其帖子),无需多次往返。Mutation 顺序执行 — 一个查询中的 mutation 按照声明顺序依次执行。Subscription 通过 WebSocket 建立持久连接,当事件发生时,服务器通过该连接发送数据。
操作可以接受 变量 以将数据与查询分离,指令(@include、@skip)以条件性地包含字段,以及 片段 以重用字段集合。这些能力使 GraphQL 查询灵活且可重用,这在包含多个屏幕和组件的大型项目中尤其重要。
GraphQL 的核心是 类型系统,它描述了所有可能的数据和 API 操作。架构(Schema)是服务器可以返回的类型以及接受的查询的描述。架构使用 Schema Definition Language (SDL) 编写,作为客户端和服务器之间的契约。客户端可以通过内省(introspection)获取架构 — 一个返回 API 完整描述的特殊查询 __schema。
博客架构示例:
// SDL — Schema Definition Language
type User {
id: ID!
name: String!
email: String
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String
author: User!
}
type Query {
user(id: ID!): User
posts(page: Int): [Post!]!
}
感叹号 (!) 表示非空字段 — 保证在响应中存在。方括号 [ ] 表示列表。GraphQL 支持标量类型(Int、Float、String、Boolean、ID)、对象类型、枚举、联合、接口和输入类型(用于变异的参数)。严格的类型化使 API 自我文档化,并允许客户端工具生成代码:TypeScript 类型、Kotlin 数据类、Swift 结构体。
内省 — GraphQL 独有而 REST 中没有的能力。客户端可以向架构发送查询并接收所有类型、字段、参数和指令的完整描述。这是 GraphiQL 和 Apollo Studio 等工具的基础,这些工具自动为开发者生成文档和自动补全。内省还允许编写自动化测试,检查架构是否符合预期结构。
在 GraphQL 和 REST 之间进行选择是设计 API 时的关键架构问题之一。两种方法各有优缺点,选择取决于项目的具体需求。REST 在简单性和通用性方面胜出,而 GraphQL 在灵活性和查询效率方面更优。让我们看看比较表格。
| 标准 | REST | GraphQL |
|---|---|---|
| 响应结构 | 固定,由服务器决定 | 灵活,由客户端决定 |
| Overfetching | 常见 — 服务器返回所有字段 | 无 — 客户端只请求需要的字段 |
| 请求数量 | 多次往返 | 一次请求获取所有数据 |
| 缓存 | 原生 HTTP 缓存 | 需要手动配置 |
| 类型化 | 未内置(取决于格式) | 严格,通过 SDL 架构 |
| 工具 | curl、Postman、Swagger | GraphiQL、Apollo Studio、内省 |
| 文件上传 | 原生支持 multipart | 需要额外协议 |
| 性能 | 可预测,更易优化 | 取决于嵌套查询的复杂度 |
GraphQL 的主要缺点是 缓存困难。在 REST 中,HTTP 缓存在 URL 级别工作:一个对 /api/users/42 的请求总是返回相同的结构,响应可以根据 URL 缓存。在 GraphQL 中,所有请求都发送到一个端点,响应的结构取决于请求体。为了解决这个问题,Apollo Client 在客户端使用规范化缓存,将响应按 id 拆分为单独的实体,并在收到新数据时自动更新它们。
另一个重要方面是 N+1 问题。在请求嵌套数据时(例如,用户的帖子和每个帖子的评论),GraphQL 可能为列表中的每个元素执行单独的 SQL 查询。这可以通过 DataLoader 解决 — 一个用于批处理和缓存数据库查询的工具,它将单独的查询合并为一个批处理查询。在 REST 中,这个问题不那么明显,因为开发者在服务器端控制响应的结构。
让我们看看在 Kotlin 移动应用中使用 Apollo Client 的 GraphQL 实际示例。这些示例展示了典型场景:为个人资料页面加载数据(query)、创建新帖子(mutation)和订阅新评论(subscription)。每个示例都包括 GraphQL 查询和客户端代码。
一个 GraphQL 查询加载用户、其最近的帖子以及关注者总数。在 REST 中,至少需要 2-3 个请求:/users/42、/users/42/posts、/users/42/stats。GraphQL 将它们合并为一次往返,从而在慢速连接上减少屏幕加载时间。
// GraphQL 查询(在 .graphql 文件中)
query ProfileScreen($userId: ID!) {
user(id: $userId) {
name
bio
avatarUrl
posts(limit: 10) {
id
title
createdAt
}
followersCount
followingCount
}
}
// 客户端调用(Apollo Client + Kotlin)
val response = apolloClient
.query(ProfileScreenQuery(userId = "42"))
.execute()
binding.nameText.text = response.data?.user?.name
变异不仅创建资源,还返回其当前数据以更新 UI。__typename 字段被 Apollo Client 用于规范化缓存 — 在收到成功的变异响应后,客户端自动更新缓存中的 Post 记录。
// GraphQL 变异
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
createdAt
author {
id
name
}
}
}
// 使用输入类型调用变异
val input = CreatePostInput(
title = "关于 GraphQL 的新帖子",
content = "GraphQL 简化了与 API 的工作……"
)
val result = apolloClient
.mutation(CreatePostMutation(input))
.execute()
在移动开发方面,GraphQL 相对于 REST 的一个重要优势是 自动代码生成。用于 Kotlin 的 Apollo Client(Apollo GraphQL)在构建阶段从 .graphql 文件生成类型安全的类。如果服务器更改了架构,项目在查询更新之前将无法构建。这防止了 REST 中常见的运行时错误,在 REST 中响应结构的更改在开发过程中可能不被注意。
GraphQL 生态系统包括几个关键的库和工具,它们简化了开发和运维。Apollo Client — 最流行的客户端库,支持 React、iOS、Android 和 Kotlin Multiplatform。Facebook 的 Relay — React 应用的替代方案,具有独特的数据管理和缓存方法。Apollo 和 Relay 之间的选择取决于平台和性能要求。
在服务器端,Apollo Server(Node.js)、Netflix DGS Framework(Kotlin/Java)和 graphql-ruby 处于领先地位。用于开发架构和测试查询的工具是 GraphiQL — 内置于浏览器的交互式 IDE。Apollo Studio 为生产环境提供性能指标、查询跟踪和架构管理。另外值得一提的是 GraphQL Code Generator — 一个从 SDL 架构生成 TypeScript、Kotlin、Swift 和 Dart 类型的工具。
对于移动开发,特别值得关注的是 Apollo Kotlin(Apollo GraphQL) — 一个完全用 Kotlin 编写的库,支持协程、Flow 和 Multiplatform。它允许在 Kotlin Multiplatform 项目中为 Android 和 iOS 使用相同的 GraphQL 查询。Apollo Kotlin 规范化缓存、支持字段级错误(partial errors)并自动从 .graphql 文件生成数据模型。这使得 GraphQL 成为大型移动项目的首选,因为在这些项目中开发速度和类型安全性非常重要。
常见问题
GraphQL 不会取代 REST,而是提供了一种替代方法。REST 更适合简单的 CRUD API、通过 HTTP 缓存以及负载可预测的公共 API。GraphQL 适用于具有大量相关数据的复杂接口。
迁移可以 逐步 进行:GraphQL 可以作为现有 REST 服务前面的中间层(网关)工作。许多公司在不关闭旧 API 的情况下将 GraphQL 与 REST 一起添加。完全替换需要重写解析器。
N+1 发生在对列表中的每个元素执行单独的数据库查询时。通过 DataLoader 解决——一个将单独的查询批处理为一个并在单个 HTTP 请求范围内缓存结果的库。
GraphQL 规范没有直接定义文件上传。实践中使用:base64 编码(简单但对大文件效率低)、根据 graphql-multipart-request-spec 协议的 multipart 请求,或单独的文件 REST 端点。
GraphQL 的安全性需要额外措施:限制嵌套深度、查询复杂度限制、操作级别的速率限制。公开的架构内省可能暴露数据结构——在生产环境中建议禁用它。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。