大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
GraphQL 概览
这一课你将能做到
- 用一句话说清楚 GraphQL 是什么、和 REST 的本质区别
- 解释「用图思考(Thinking in Graphs)」并把本站业务(课时、用户、进度)画成一张图
- 发出第一个 GraphQL 查询,并读懂返回的 JSON
为什么要学
用 REST 做「课时详情页」这种聚合页面时,你通常会经历这样的对话:前端说「我还需要讲师名字和课时数」,后端说「那我再加两个接口」,或者干脆在一个接口里塞一堆不一定用得上的字段。前者叫多次往返(under-fetching),后者叫过度获取(over-fetching)。
GraphQL 是 Meta(Facebook)2012 年内部发明、2015 年开源的一种查询语言 + 运行时。它的核心思想只有一条:客户端精确描述自己要什么数据,服务器一次返回不多不少。接口长什么样由一份强类型的 Schema 决定,前后端都照它开发,文档自动生成,不用再维护一份永远过时的接口 Wiki。
人话总结:REST 是「菜单上有什么你吃什么」,GraphQL 是「你点菜,厨房照单出」。
先建立图像
REST 风格取「课时 + 作者 + 我的进度」要三个请求:
GET /api/lessons/101 → 课时基本信息
GET /api/lessons/101/author → 作者信息
GET /api/users/me/progress?lessonId=101 → 我的进度
GraphQL 风格,一个请求、按图遍历:
query {
lesson(slug: "gqlrm-intro") { # 从一个节点出发
title # 只要标题
author { name } # 顺着边走到作者节点,只要名字
myProgress { status percent } # 再走到我的进度节点
}
}
返回结构和查询形状一一对应:
{
"data": {
"lesson": {
"title": "GraphQL 概览",
"author": { "name": "教研组" },
"myProgress": { "status": "IN_PROGRESS", "percent": 40 }
}
}
}
把业务画成图(节点 = 类型,边 = 字段):
Track ──has many──> Lesson ──belongs to──> Author(User)
│ │
│ └──has many──> Submission
│ │
User ──has many──> Progress <─────────────┘
跟着做
步骤 1:用 curl 发第一个查询
任何 GraphQL 服务都是一个 HTTP 端点(通常是 POST /graphql),请求体是 JSON:
curl -s http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ lesson(slug: \"gqlrm-intro\") { title durationMinutes } }"}'
响应永远是 { "data": ... } 或 { "errors": [...] }(或两者都有)。
步骤 2:用 GraphiQL / Apollo Sandbox 探索
打开 http://localhost:8080/graphiql(大多数服务端库自带这个 IDE)。左侧写查询,Ctrl+Enter 执行,右侧出结果。点「Docs」能看到整张 Schema 的可点击文档——这就是「自文档化」的实际含义。
步骤 3:体会「要什么给什么」
同一个端点,两次请求拿不同字段,服务端代码一行不改:
# 列表页:轻量
{ lessons(first: 10) { title slug } }
# 详情页:重量
{ lesson(slug: "gqlrm-intro") {
title slug content
track { title }
author { name avatarUrl }
} }
步骤 4:故意写错,看校验
{ lesson(slug: "gqlrm-intro") { titl } } # titl 拼错了
返回:"errors": [{"message": "Cannot query field 'titl' on type 'Lesson'. Did you mean 'title'?"}]。强类型 Schema 在执行前就拦住错误,这是 REST 给不了的体验。
讲清楚要点
- GraphQL 是查询语言,不是数据库:名字里的 QL 容易误导。它不存数据,只定义「怎么问」和「怎么答」,数据从哪来(MySQL、Redis、别的微服务)由你写的 Resolver 决定。
- 单一端点 + 强类型 Schema:所有操作走
/graphql,Schema 是前后端唯一契约。 - 三个根操作:Query(读)、Mutation(写)、Subscription(订阅推送),后面各有专课。
- 响应形状镜像查询形状:客户端能精确预测 JSON 结构,TypeScript 类型可以据此自动生成。
- 没有版本号:演进方式是「加字段、弃用旧字段(@deprecated)」,而不是
/v2/graphql。 - GraphQL 不是银弹:简单 CRUD、文件上传、CDN 缓存友好的场景,REST 可能更省事。它适合数据关系复杂、客户端多样(Web/iOS/Android)的产品——比如本站这种课时-进度-提交互相嵌套的学习平台。
你可能会懵的地方
- GraphQL 会取代 REST 吗? 不会,两者可以共存。很多团队在 BFF(前端专属网关层)用 GraphQL 聚合,内部服务间仍然用 REST/gRPC。
- 一个端点怎么做监控和限流? 不能按 URL 统计了,要按「操作名 + 字段成本」统计,后续「服务端执行」和「规范与传输」两课会讲。
- 客户端随便拼查询,会不会把数据库打爆? 会,这叫恶意深度查询。生产环境必须限制查询深度和复杂度,属于安全基线。
- GraphQL 是不是必须配 React? 完全无关。GraphQL 是协议,curl、Java 的 RestTemplate、iOS 的 URLSession 都能调。
- 为什么响应里有个 data 包一层?
规范如此:
data放成功结果,errors放错误,部分成功时两者同时存在——这是 GraphQL 容错模型的核心。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 把 GraphQL 当 ORM 用 | 它不操作数据库,查的是图不是表 | Resolver 里再调你的 Service/DAO |
| 为每个页面建一个 REST 接口 | 接口爆炸、文档腐烂 | 一个 Schema,客户端自由组合字段 |
| 以为 GET /graphql 一定能查 | 很多服务只允许 POST | 看服务端对 GraphQL-over-HTTP 的支持情况 |
| 一次返回整个对象所有字段 | 退化回 over-fetching | 查询里只声明页面真正用到的字段 |
| 给 API 加 /v1 /v2 前缀 | 违背 GraphQL 演进模型 | 新增字段 + @deprecated 旧字段 |
卡点急救
- 发请求返回 404:端点路径不一定是
/graphql,看服务端配置(Spring for GraphQL 默认/graphql,Apollo Server 默认也是/graphql或/)。 - 返回 400 且错误信息是 "Cannot query field...":字段名拼错或 Schema 没这个字段,打开 GraphiQL 的 Docs 面板核对。
- curl 一直报 "Must provide query string":
-d里的 JSON 引号转义有问题,把请求体写到文件用-d @body.json,或改用 IDE 调试。
面试常问
- GraphQL 和 REST 的核心区别? REST 以资源为中心、多端点、服务端决定返回结构;GraphQL 以图为中心、单端点、客户端声明返回结构。补一句:GraphQL 解决 over/under-fetching,代价是缓存和限流更复杂。
- 什么场景不适合 GraphQL? 简单 CRUD、强 HTTP 缓存依赖(CDN)、文件流传输、团队没有治理复杂查询能力的场景。
- GraphQL 的「强类型」体现在哪? Schema 用 SDL 声明每个字段的类型与可空性,查询在执行前先经过校验,非法查询直接报错不执行。
自测三问
- over-fetching 和 under-fetching 分别指什么?GraphQL 怎么同时解决两者?
- 一次 GraphQL 请求可以同时成功一部分、失败一部分吗?响应长什么样?
- 为什么说 Schema 是前后端的「唯一契约」?
小结一句话
GraphQL 让客户端用一份强类型的图契约,一次请求精确取回所需数据,代价是把复杂度从「接口数量」转移到了「Schema 与查询治理」。
延伸
- GraphQL 官方入门(中文)
- GraphQL 规范
- 下一课:类型系统——Schema 的砖块:标量、对象、枚举、接口与联合。