RealWorld 规范中心实战指南:openapi.yml 契约、Hurl 测试套件与 Playwright 前端校验是如何支撑 100+ 框架实现的
RealWorld 被称为 “The mother of all demo apps”:它用同一套 Medium.com 克隆应用(Conduit)规范,驱动了上百种语言与框架的前后端实现。本篇以仓库根目录 README.md 为主线,结合 specs/api/openapi.yml、specs/api/hurl 与 specs/e2e 等真实文件,讲清楚这个“规范与文档中心”仓库的模块化设计、如何对后端实现跑 API 测试、对前端实现跑共享 E2E 测试,以及如何本地构建其文档站点,帮助你在选型、学习或新增一个 RealWorld 实现时有的放矢。
1. 定位:同一份 API 契约,任意前端搭配任意后端
README.md 的核心主张是:大多数 “todo 示例” 只能让你粗略了解一个框架的能力,却传达不出构建真实应用所需的知识,也暴露不出最小 demo 从不需要面对的真实世界约束;而 RealWorld 的做法是为每个框架提供同一个 demo 应用,落在“简单性与覆盖面之间的甜点区”。
其模块化根基在于:所有前端与后端实现都遵循同一份 API 规范(README 原文:“Any frontend can be combined with any backend, because they all adhere to the same API spec”)。这意味着:
- 你可以用任何框架写后端,只要它能通过完整的 API 规范测试套件;
- 你可以用任何框架写前端,只要它使用共享 CSS 主题并通过共享 E2E 套件;
- 前后端实现彼此解耦,可以独立开发、独立验证、自由组合。
README 同时列出了几个关键事实:
- 已有 100 多种实现,使用不同的语言、库和框架;
- 两个“规范合规”(spec-compliant)后端实现被单独点名:Nitro + Prisma + Zod(TypeScript) 与 Django Ninja(Python),它们通过了完整的 API 规范测试套件;
- 仓库提供共享 CSS 主题 让前端实现拥有完全一致的 UI/UX,提供共享 E2E 测试套件 用于验证前端实现;
- 维护者包括 c4ffein(维护规范、测试套件与演示站点)与 Manuel Vila(Layr 框架与 CodebaseShow 的创造者)。
2. 仓库目录结构:规范中心而非可运行应用
与许多教程仓库不同,这个仓库本身不是一个可运行的应用。CLAUDE.md 开宗明义:“This is the RealWorld spec & docs hub — not an implementation. Nothing here is a runnable app.” 各目录职责如下:
| 路径 | 角色 |
|---|---|
| specs/api/ | API 契约本体:openapi.yml 加上后端必须通过的 Hurl 与 Bruno 测试套件 |
| specs/e2e/ | 用于验证前端的共享 Playwright 套件,含选择器契约 SELECTORS.md 与基线配置 playwright.base.ts |
| docs/ | 基于 Astro/Starlight 构建的文档站点源码(含 package.json、astro.config.mjs、tsconfig.json) |
| assets/ | 共享前端素材:CSS 主题、Logo 生成器 generate_assets.py 与各平台图标 |
| Makefile | 仓库的入口命令集合,make help 查看全部目标 |
Makefile 将仓库的两大工作流收敛为两组目标:Bruno 集合的生成/校验(bruno-generate、bruno-check)与文档站点的生命周期(documentation-setup、documentation-dev、documentation-dev-host、documentation-build、documentation-preview、documentation-clean)。
3. API 规范本体:openapi.yml 定义了哪些端点
specs/api/openapi.yml 是一份 OpenAPI 3.1.0 文档,标题为 “RealWorld Conduit API”,版本 2.0.0,按六大业务域打标签:Articles、Comments、Favorites、Profile、Tags、User and Authentication。从文件中的 paths 定义可以看到完整的端点面:
- 认证与当前用户:
POST /users/login(登录,401/422 语义)、POST /users(注册,201/409/422)、GET /user与PUT /user(均需Token认证); - 文章:
GET /articles/feed(只返回你所关注用户的最新文章,要求认证,支持limit/offset)、GET /articles(全局最新,可选按tag、author、favorited过滤并支持分页)、POST /articles(创建)、以及按 slug 的单篇 GET/PUT/DELETE; - 个人主页:
GET /profiles/{username}(认证可选)、POST|DELETE /profiles/{username}/follow(关注/取关,要求认证); - 评论与收藏:围绕文章 slug 的评论增删查,以及文章收藏/取消收藏。
值得注意的是,该契约不仅描述“正常路径”,还通过响应定义(如 Unauthorized、NotFound、ConflictError、GenericError)与测试套件共同约束了边界行为。例如注册重名/重邮返回 409,未认证访问受保护资源返回 401——这些在 specs/api/hurl 的 errors 系列文件中都有逐条断言(见下节)。
specs/api/README.md 对该目录的定位是:它定义了契约,并附带了“后端必须通过”的测试套件;README 中还提到对外的公开后端 api.realworld.show 无需 API key 即可使用(提供演示账号,且真实账号之间互相不可见),以及配套的 Angular 前端演示站点 demo.realworld.show。
4. 后端验证:Hurl 是事实来源,Bruno 是生成的镜像
4.1 运行 API 测试
对任意一个运行中的后端,只需把 HOST 指向它,即可在 specs/api/ 目录下执行(出自 specs/api/README.md):
HOST=http://localhost:3000/api ./run-api-tests-hurl.sh
HOST=http://localhost:3000/api ./run-api-tests-bruno.sh
从 run-api-tests-hurl.sh 源码可以看到执行细节:
hurl --test \
--jobs 1 \
--variable "host=$HOST" \
--variable "uid=$UID_VAL" \
"${FILES[@]}"
HOST默认http://localhost:8000,可用环境变量覆盖;uid默认取$(date +%s)$$(时间戳+进程号),用于为每次运行生成互不冲突的用户名(如auth_{{uid}}),避免测试账号串号;--jobs 1保证请求按顺序串行执行——因为这些.hurl文件是“多步场景脚本”,前面的 [Captures] 会被后面的 [Asserts] 依赖;- 不带参数时默认运行
hurl/*.hurl的全部文件,也可以把特定.hurl文件作为参数传入,只跑某个领域(如./run-api-tests-hurl.sh hurl/pagination.hurl)。
Bruno 侧的 run-api-tests-bruno.sh 则按子文件夹逐个执行 bun x @usebruno/cli run <folder> --env local --env-var "host=$HOST" --sandbox safe,默认跳过 environments/ 目录。你当然也可以直接在 Bruno 应用中打开 specs/api/bruno 目录交互式地查看和运行请求。
4.2 测试套件的组织方式
hurl/ 目录按业务域 + 错误类别组织了 13 个场景文件:articles.hurl、auth.hurl、comments.hurl、favorites.hurl、feed.hurl、pagination.hurl、profiles.hurl、tags.hurl,以及 errors_auth.hurl、errors_articles.hurl、errors_authorization.hurl、errors_comments.hurl、errors_profiles.hurl。Bruno 集合则是按同结构镜像的目录(articles/、auth/、comments/ 直到 errors-* 系列),文件以编号命名保证执行顺序。
以 auth.hurl 为例,一个文件就是一条完整的场景链:注册 → 登录 → 获取当前用户 → 更新 bio → 验证持久化 → 空字符串 bio 归一化为 null → null 赋值被接受 → 更新 image → image 空串归一化 → 更新 username/email 并验证。每个请求都带 [Asserts](jsonpath 精确断言,如 jsonpath "$.user.bio" == null)与 [Captures](如 token: jsonpath "$.user.token" 供后续请求的 Authorization: Token {{token}} 头复用)。
这些断言揭示的正是“规范合规”的深意——它校验的不只是 CRUD 可用性,还包括一批容易在真实实现里踩坑的边界行为:
- 可空字段的空字符串归一化:
PUT /user把bio/image设为""必须返回null,且归一化要持久化(errors_auth.hurl 的 06/07/14/15 号文件及 specs/api/bruno/auth 中的 06、14 等用例逐条验证); - 必填字段的空值拒绝:
username/email更新为空字符串或 null、密码空串或不足 8 位必须被拒绝(specs/api/bruno/errors-auth 的 12–18 号用例); - 重复标题允许:
12-duplicate-titles-are-allowed-each-gets-a-unique-slug.bru验证两个同名文章各自获得唯一 slug; - 标签更新语义:更新文章时不带
tagList应保留原标签,空数组[]应真正清空,而null应被拒绝(specs/api/bruno/articles 的 12–15 号用例); - 跨用户授权:user B 删除/更新 user A 的文章或评论必须得到 403,且失败的删除不得产生副作用(specs/api/bruno/errors-authorization);
- 分页语义:
limit/offset组合下最新文章优先,offset 1 取到第二页(specs/api/bruno/pagination)。
4.3 Hurl 为源、Bruno 生成:单一事实来源的工程纪律
specs/api/README.md 明确声明:“The Hurl files are the source of truth. The Bruno collection is generated with make bruno-generate and kept in sync via CI (make bruno-check).” 对应 Makefile 中的实现:
bruno-generate:
bun specs/api/hurl-to-bruno.js
bruno-check:
bun specs/api/hurl-to-bruno.js --check
即:改动测试用例时只改 Hurl 文件,然后用 hurl-to-bruno.js 重新生成 specs/api/bruno;CI 通过 --check 模式失败于任何“手工改 Bruno”的漂移。CLAUDE.md 同样强调这条纪律:“Don't hand-edit specs/api/bruno/ — change the Hurl files and regenerate.” 这套“一个源、一个生成器、一个 CI 检查”的模式,保证了人类可读的交互式集合与可自动化的脚本测试永不失同步。
5. 前端验证:共享 Playwright E2E 套件与选择器契约
对前端实现,仓库提供的是 specs/e2e/:一组可直接被实现仓库“vendored”(作为 ./e2e 引入)的 Playwright 测试。场景文件覆盖 auth.spec.ts、articles.spec.ts、comments.spec.ts、navigation.spec.ts、settings.spec.ts、social.spec.ts、error-handling.spec.ts、null-fields.spec.ts、xss-security.spec.ts 等,helpers/ 目录则封装了 API 辅助(api.ts)与各领域操作(articles.ts、auth.ts、comments.ts、profile.ts、setup.ts)。
集成方式在 playwright.base.ts 的注释中给出官方模板:实现在自己的根目录 playwright.config.ts 中扩展基线配置、覆盖 baseURL 并声明 webServer:
import { defineConfig } from '@playwright/test';
import { baseConfig } from './e2e/playwright.base';
export default defineConfig({
...baseConfig,
use: { ...baseConfig.use, baseURL: 'http://localhost:3000' },
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
基线配置本身的取舍值得注意:testDir: './e2e'、fullyParallel: false、workers: 1(严格串行,规避共享状态的竞态)、retries: CI ? 2 : 1、forbidOnly: !!CI(CI 中禁止遗留 test.only)、trace: 'on-first-retry' 与 screenshot: 'only-on-failure'(失败时保留取证材料),并只配置了 Desktop Chrome 项目。
前端还必须满足 SELECTORS.md 定义的选择器契约——从源码结构看,这是为了让任何框架的前端都能被同一套测试以统一的 testid/CSS 选择器定位,从而让“通过共享 E2E”成为前端实现合规的客观判据。
6. 文档站点:Astro/Starlight 源码与 Makefile 入口
docs/ 目录是一个完整的 Astro 站点工程(docs/package.json、docs/astro.config.mjs、docs/bun.lock),内容组织在 docs/src/content/docs 下,与 README 的 “Learn more” 一一对应:specifications/backend(endpoints.md、api-response-format.md、error-handling.md、tests.md 等)、specifications/frontend(api.md、styles.md、tests.md、templates.md、routing.md)、implementation-creation(introduction.md、features.md、expectations.md)以及 community 文档。
本地工作流全部收敛在 Makefile 中(注意仓库约定使用 bun 而非 npm):
make documentation-setup # cd docs && bun install
make documentation-dev # 本地开发服务器
make documentation-dev-host # 暴露到局域网
make documentation-build # 生产构建
make documentation-preview # 预览构建产物
make documentation-clean # 清理 .astro / dist / node_modules
7. 使用指引:规范中心的三条红线
综合 README.md 与 CLAUDE.md,这个仓库的使用者画像有明确分工:
- 如果你是在为某个框架创建 RealWorld 实现:实现位于各自独立的仓库中,不在本仓库。后端对着 specs/api/ 开发并用第 4 节的脚本验收;前端使用 assets/theme/styles.css 主题并引入
specs/e2e/做验收;完整入门与期望见 docs/src/content/docs/implementation-creation/introduction.md。 - 如果你的实现仓库以 submodule/依赖形式内嵌了本仓库(E2E 套件即被这样以
./e2e消费):CLAUDE.md 给出的规则是“修你的实现,永远不要为了通过而编辑规范或测试——specs/下的文件是失败实现必须服从的事实来源”。 - 仓库内部约定:一切用
bun;Hurl 是 API 套件的事实来源,Bruno 是生成镜像;文档 URL 带尾斜杠。贡献流程与提交信息规范(<type>(<scope>): <subject>,type 限docs/feat/fix,scope 限specs/project)见 CONTRIBUTING.md;框架 Logo 的授权与署名细节见 docs/non-included/LICENSES_LOGOS.md。
8. 小结
RealWorld 主仓库的价值不在某一份框架代码,而在于它把“跨栈互操作”变成了可验证的工程事实:一份 openapi.yml 契约 + 一套 Hurl 事实来源/Bruno 镜像的后端测试 + 一套 Playwright/选择器契约的前端测试 + 一个共享 CSS 主题。理解了这套“契约先行、测试即验收标准”的结构,你无论是想快速吃透某个框架如何写真实业务应用(去对应的实现仓库学习),还是想为尚未覆盖的框架新增一个实现(对着本仓库的规范与测试自证合规),都有了清晰的路线图。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00