首页
/ RealWorld 后端实现指南:以 OpenAPI 与 Hurl 测试套件定义的 API 契约

RealWorld 后端实现指南:以 OpenAPI 与 Hurl 测试套件定义的 API 契约

2026-09-04 10:53:15作者:虞亚竹Luna

RealWorld 是“the mother of all demo apps”——同一套 Medium 风格应用契约被 React、Angular、Node、Django 等众多技术栈共同实现,其前提是所有后端都严格遵循同一份 API 规范。本文基于仓库中的后端规范入口文档 introduction.md 展开,讲清契约的三个载体(OpenAPI 规范、Hurl 测试套件、Bruno 集合)、本地运行测试的完整命令与参数,以及“测试即唯一事实来源”这一核心原则,读完即可直接启动后端开发并用自带测试套件验收你的实现。

一、契约优先:所有后端实现必须遵循同一份 API 规范

规范文档开宗明义:所有后端实现都必须遵循 specs/api 目录下的 API 规范。完整的接口定义以机读形式存放在 openapi.yml,这是唯一描述全部接口行为的 OpenAPI 规范文件。

该入口文档特别用提示框强调了后端规范中最重要的原则:

测试是事实来源(The tests are the source of truth)。 文档页面只是用自然语言对契约的总结,真正定义契约的是 OpenAPI 规范Hurl 测试套件。当文字描述与测试不一致时,以测试为准——对着测试来开发,发现不一致时请提交 issue。

这条原则对多语言、多框架的 RealWorld 生态至关重要:几十个技术栈的后端要实现互操作,散文式的描述允许歧义,而可执行的请求-断言序列不会。因此规范文档的定位是“快速索引和可读摘要”,可执行测试才是验收标准。

二、API 契约覆盖的接口面

配合 OpenAPI 规范与 endpoints.md 文档,当前契约覆盖的接口可以归为五组(所有路由均以 /api 为前缀,认证方式为请求头 Authorization: Token jwt.token.here):

分组 接口 认证要求
认证 POST /api/users/loginPOST /api/usersGET /api/userPUT /api/user 前两者免认证,后两者必须认证
资料与关注 GET /api/profiles/:usernamePOST /api/profiles/:username/followDELETE /api/profiles/:username/follow 获取资料可选认证,关注/取关必须认证
文章 GET /api/articles(支持 tag/author/favorited/limit/offset 查询参数)、GET /api/articles/feedGET /api/articles/:slugPOST /api/articlesPUT /api/articles/:slugDELETE /api/articles/:slug 列表与详情免认证,写操作必须认证
评论 POST /api/articles/:slug/commentsGET /api/articles/:slug/commentsDELETE /api/articles/:slug/comments/:id 创建与删除必须认证,列表可选
收藏与标签 POST /api/articles/:slug/favoriteDELETE /api/articles/:slug/favoriteGET /api/tags 收藏操作必须认证,标签免认证

几个在实现时容易忽略、但测试会严格校验的细节:

  • 列表分页参数GET /api/articleslimit 默认 20、offset 默认 0,结果按最新在前排序;GET /api/articles/feed 同样接受 limit/offset,但只返回已关注用户创建的文章。
  • slug 更新语义PUT /api/articles/:slug 修改 title 时 slug 必须随之更新;规范只要求 slug 是可用于取/改/删的唯一字符串,重复标题也必须生成不同 slug,格式本身不强制(常见做法是 kebab-case)。
  • 空字符串归一化:用户资料的 bioimage 属于可空字段,提交空字符串应被归一化为 null 并持久化——这一行为在 auth.hurl 中有专门的用例覆盖(如“Update user bio to empty string - should normalize to null”)。

三、用 Hurl 测试套件验证你的后端

入口文档给出的核心便利设施是 specs/api/hurl 目录下的 Hurl 测试集合:在你实现 API 端点的过程中随时跑一遍即可对照契约。全套测试由脚本 run-api-tests-hurl.sh 一键执行:

HOST=http://localhost:3000/api ./run-api-tests-hurl.sh

specs/api 的 README 中也给出了完全一致的命令(把 HOST 指向你的后端根路径即可)。阅读脚本源码可以确认其参数语义:

  • HOST 环境变量:被测后端地址,默认值为 http://localhost:8000(脚本内 HOST="${HOST:-http://localhost:8000}");
  • UID_VAL 环境变量:默认取 date +%s 拼接进程号,作为测试数据的唯一后缀,保证多次运行不产生用户名/邮箱冲突;
  • 底层执行 hurl --test --jobs 1,串行执行以维持请求间的依赖顺序,并把 hostuid 注入为 Hurl 变量;
  • 位置参数可用于只跑部分文件:不带参数时执行 hurl/ 下全部 .hurl 文件,带参数则只跑指定文件。

测试文件按功能域划分,覆盖正常流与错误流两类场景:

auth.hurl 中的注册请求为例,可以看到 Hurl 用例的完整写法——请求、预期状态码、jsonpath 断言与变量捕获:

# Register
POST {{host}}/api/users
{
  "user": {
    "username": "auth_{{uid}}",
    "email": "auth_{{uid}}@test.com",
    "password": "password123"
  }
}
HTTP 201
[Asserts]
jsonpath "$.user.username" == "auth_{{uid}}"
jsonpath "$.user.email" == "auth_{{uid}}@test.com"
jsonpath "$.user.bio" == null
jsonpath "$.user.image" == null
jsonpath "$.user.token" isString
jsonpath "$.user.token" not isEmpty
[Captures]
reg_token: jsonpath "$.user.token"

这段用例同时验证了三个契约点:注册返回 201 而非 200;新用户 bio/image 必须为 nulltoken 必须是非空字符串。捕获的 token 会供后续用例(如 Authorization: Token {{token}})复用,整条链路因此能自动验证“注册→登录→取当前用户→更新资料→验证持久化”的完整生命周期。

四、Bruno 集合:与 Hurl 同步的 GUI 工作流

如果偏好图形化调试,specs/api/bruno 目录下提供了 Bruno 请求集合,其结构(auth/articles/errors-* 等文件夹及编号命名的 .bru 文件)与 Hurl 用例一一对应,可直接用 Bruno 应用打开交互式执行。它的执行脚本为 run-api-tests-bruno.sh

HOST=http://localhost:3000/api ./run-api-tests-bruno.sh

脚本逐文件夹调用 bun x @usebruno/cli run,通过 --env local 读取 environments/local.bru 中的 host 变量(默认 http://localhost:3000),并支持以位置参数指定子集、BRUNO_SANDBOX 环境变量(默认 safe)控制沙箱级别。

从源码结构看,Bruno 集合不是手工维护的第二套测试:它由 hurl-to-bruno.js 从 Hurl 文件解析生成(按注释行、方法行、请求体分段解析后写出 .bru 文件),仓库 Makefile 提供 make bruno-generate 生成、make bruno-check 在 CI 中校验同步。specs/api/README.md 明确指出:Hurl 文件是事实来源,Bruno 集合是生成物——这与入口文档“测试优先于散文”的原则一脉相承。

五、契约的其他关键部分:响应格式与错误处理

入口文档在结尾引导读者继续阅读后端规范的其余三页,这里给出与测试用例相互印证的核心要点,便于实现时一次性对齐:

响应格式(详见 api-response-format.md):所有对象包裹在单数键下,如 {"user": {...}}{"article": {...}}{"profile": {...}}{"tags": [...]},列表接口额外返回 articlesCount。需要特别注意的是,自 2024-08-16 起,GET /api/articlesGET /api/articles/feed 不再返回文章的 body 字段(出于性能考虑),文章正文只能通过 GET /api/articles/:slug 获取——这是测试与文档共同约束的行为。

错误处理(详见 error-handling.md):校验失败返回 422,错误体格式为:

{
  "errors": {
    "body": ["can't be empty"]
  }
}

此外契约约定:401 表示缺少认证、403 表示无权操作(例如 B 用户删除 A 用户的文章)、404 表示资源不存在。这些状态码分别由 errors-autherrors-authorizationerrors-articles 等错误流用例逐一验证。

六、小结:对着测试构建,按规范索引深入

RealWorld 后端规范的阅读路径可以概括为三步:

  1. OpenAPI 规范 为接口全集,快速索引见 endpoints.mdapi-response-format.mderror-handling.md
  2. 开发过程中随时运行 HOST=<你的地址> ./run-api-tests-hurl.sh(或 Bruno 等价流程)做回归验证,Hurl 文件位于 specs/api/hurl,运行细节见 hurl.mdbruno.md
  3. 文档与测试不一致时以测试为准,并把差异作为 issue 反馈。

遵循这条路径,你的后端实现就与 RealWorld 生态中其他技术栈的实现处于同一契约之下。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341