RealWorld 前端 API 接入指南:本地后端与 Demo API 两种测试方案及内容可见性限制
本文围绕 RealWorld 前端实现的 API 测试展开:讲解如何把前端对接到官方后端(本地运行的 Nitro + Prisma + Zod 实现)或官方托管的 Demo API 上完成联调,并结合仓库中的 E2E 测试代码与 API 测试套件,说明 API 地址的配置方式、认证请求的实际格式,以及公共 API 自 2021 年起引入的用户内容可见性限制及其对测试设计的影响。
为什么前端实现需要一个统一的 API 契约
RealWorld 的核心设计是"同一个 Medium 克隆应用可以用任意前端框架 + 任意后端实现组合",前提是双方都遵守同一份 API 规范。仓库中的 OpenAPI 定义 就是这份契约的机器可读版本,其中 servers 字段直接指向官方 Demo API:
servers:
- url: https://api.realworld.show/api
这意味着前端开发者不需要先写完一个后端才能开始开发——文档 docs/src/content/docs/specifications/frontend/api.md 给出了两条明确的联调路径:
- 本地运行官方后端实现,适合需要调试请求细节、断点排错的场景;
- 直接指向官方托管的 Demo API,零部署成本,适合快速启动一个纯前端实现。
两条路径共用同一套端点与请求/响应结构,因此前端代码在两者之间切换时通常只需改一个基地址配置。
方案一:本地运行官方后端实现
官方后端实现是开源的,技术栈为 Nitro + Prisma + Zod(TypeScript)。它是通过完整 API 规范测试套件的 spec-compliant backend 之一,仓库 README 在 "Spec-compliant backends" 一节中列出了它以及 Django Ninja 两个通过全部 API 测试的后端。
本地跑起来之后,默认监听 localhost:3000,这一点可以从仓库中 API 测试工具链的默认配置得到印证:Bruno 本地环境变量 定义如下
vars {
host: http://localhost:3000
}
而 Hurl 测试脚本 中 HOST 变量的默认值是 http://localhost:8000(Django 实现的默认端口),可用环境变量覆盖:
HOST=http://localhost:3000/api ./run-api-tests-hurl.sh
也就是说,本地联调的完整闭环是:启动官方后端 → 前端把 API 基地址指到 http://localhost:3000/api → 用仓库内置的 Hurl / Bruno 套件(见 specs/api/README.md)对后端本身做一次合规性验证。其中 Hurl 文件是 source of truth,Bruno 集合由 make bruno-generate 生成并经 make bruno-check 保持同步。
方案二:直接指向官方 Demo API
对于不想维护后端的纯前端实现,官方提供公共托管 API,接入方式就一行配置:
https://api.realworld.show/api
仓库的 E2E 测试正是以它作为默认后端。specs/e2e/helpers/config.ts 中定义:
export const API_BASE = process.env.API_BASE || 'https://api.realworld.show/api';
即 默认就是 Demo API,可通过 API_BASE 环境变量覆盖。例如联调本地后端时:
API_BASE=http://localhost:3000/api npx playwright test
Demo API 的使用边界
该 API 免费公开,但仅允许 RealWorld 场景使用:它不允许脱离前端应用被单独消费(即不能作为通用 API 服务被任意第三方直接调用)。同时,它无需 API Key,并内置了一批 demo 账号供测试跨用户场景使用。E2E 配置注释中明确提到,跨用户测试会复用 demo 后端预置的种子用户(如 johndoe):
seeded demo users (e.g.
johndoe) for cross-user scenarios —— 摘自 specs/e2e/helpers/config.ts 头部注释
仓库 E2E 测试对 Demo API 的实际调用方式
以下三个代码事实展示了前端实现对接该 API 时的标准模式:
1. 注册与登录返回 JWT —— specs/e2e/helpers/api.ts 中:
export async function registerUserViaAPI(request: APIRequestContext, user: UserCredentials): Promise<string> {
const response = await request.post(`${API_BASE}/users`, {
data: { user: { username: user.username, email: user.email, password: user.password } },
});
...
return data.user.token;
}
请求体包裹在 user 字段下,响应中的 user.token 就是后续认证的 JWT。
2. 认证头格式为 Token <jwt> —— 创建文章时的写法:
headers: { Authorization: `Token ${token}` }
注意这不是 Bearer,而是 Token 前缀,这是 Conduit 风格 API 的约定,前端实现必须照此设置请求头。
3. 健康检查直接探测 API 可达性 —— specs/e2e/health.spec.ts 中有一条测试用例对 https://api.realworld.show/api/tags 发起 GET 请求并断言 response.ok(),且该用例通过 EXTERNAL_API 能力开关门控(见下文测试模式小节)。
测试模式(TEST_MODE)与 API 假设的关系
Demo API 的可用性还取决于前端实现的架构形态。specs/e2e/helpers/config.ts 定义了三种测试模式与两个正交的能力标志:
| TEST_MODE | 形态 | BROWSER_API | EXTERNAL_API |
|---|---|---|---|
spa(默认) |
浏览器直接调 REST API,JWT 存客户端 | true | true |
ssr |
服务端代为调用外部 REST API(如 SvelteKit/Next.js SSR,httpOnly cookie 认证) | false | true |
fullstack |
前端自带完整前后端栈 | false | false |
这对应到 Demo API 使用上的差异:
- spa 模式下浏览器自身发起 API 调用并持有 JWT,测试可以拦截 API 流量(
page.route())、向localStorage注入 token,并等待浏览器侧的 API 响应; - ssr 模式下浏览器侧没有 API 流量可拦截,但测试运行器本身仍可直连 Demo API 做快速数据准备,种子 demo 用户同样可用;
- fullstack 模式下不假设存在独立可达的 API,一切通过 UI 驱动,跨用户场景自建账号,因此 E2E 套件中依赖外部 API 的用例(如
health.spec.ts中的/tags探测)会被自动跳过。
resolveMode() 还支持旧的 API_MODE 布尔变量做向后兼容(API_MODE=false 等价 fullstack,否则 spa)。对只想"前端 + Demo API"跑通联调的读者,保持默认的 spa 模式即可。
API 限制:用户内容可见性规则
文档特别强调的一条限制(自 2021 年引入,目的是避免公共 API 需要内容审核):
用户内容的可见性被限制:
- 未登录用户只能看到 demo 账号创建的内容;
- 已登录用户只能看到自己的内容和 demo 账号创建的内容。
README 用同样的语义复述了这一规则:
no API keys required — demo accounts are provided, and real accounts can't see each other
这条限制对前端开发和测试有三个直接影响:
- 首页/文章流断言:未登录状态下打开首页,列表内容应当只包含 demo 账号(如
johndoe等种子用户)的文章;任何"看到其他真实注册用户文章"的断言在 Demo API 上必然失败,这并非 bug。 - Feed 场景设计:关注流测试必须基于 demo 账号或自注册账号构建。仓库的 feed 测试(specs/api/bruno/feed/ 系列,如
03-feed-for-new-user-returns-empty.bru)正是先注册新用户验证空 feed、再关注、发文章、最后断言 feed 内容的模式,这套模式在本地后端与 Demo API 上均可复现。 - 跨用户隔离验证:注册两个真实用户 A/B 后,A 发布的文章对 B 不可见(除非 B 通过 feed 关注了 A)。仓库的授权错误测试 specs/api/bruno/errors-authorization/ 覆盖了这类"用户 B 不能删除/更新 A 的内容"(403)场景。
因此,如果你的前端测试用例依赖"用户之间互相可见"这一假设,要么改用本地后端(无此可见性限制,行为更接近真实部署),要么将用例重构为"自见 + demo 账号"的可见性模型。
延伸阅读与仓库内相关资源
- API 契约定义:specs/api/openapi.yml(OpenAPI 3.1,覆盖 Articles / Comments / Favorites / Profile / Tags / User and Authentication 六大标签)
- API 合规测试套件:Hurl 版本 specs/api/hurl/、Bruno 版本 specs/api/bruno/,以及说明文档 specs/api/README.md
- 前端 E2E 共享套件(验证前端实现用):specs/e2e/,配置基座 specs/e2e/playwright.base.ts
- 前端规范其他章节:路由、样式、模板、前端测试
- 后端规范介绍:specs/api 测试与后端规格
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