Nhost GraphQL 接口测试这件事,我不是第一次踩坑了。之前在一个 Nuxt.js 项目里接 Nhost 做后端,页面渲染好好的,一到用户提交数据就偶发 500,排查了半天才发现是权限规则漏了一条,导致某些角色写入失败。从那时候起我就意识到:Nhost 这类 BaaS 平台虽然把 Postgres 和 GraphQL 引擎都封装好了,但接口可靠性根本不是“能用就行”,而是要靠一套系统化测试兜底的。这篇文章就把我后来总结出来的 3 步实战方法完整拆开来讲,包括环境搭建、用例设计和自动化集成,适合正在用 Nhost 做 Nuxt.js 项目的开发者参考,也适合想给自己的 GraphQL 接口补测试的团队借鉴。
1. 为什么 Nhost GraphQL 接口测试值得单独成篇
先说结论:Nhost 本身的定位是开源的 Backend-as-a-Service,它把 PostgreSQL、GraphQL API、身份认证、存储、Serverless 函数都打包好了,你不需要自己维护后端服务。这在开发效率上是巨大的提升,但“不用写后端”不等于“不用测接口”。恰恰相反,正因为 GraphQL 这一层是平台生成的,它的可靠性问题往往藏得更深:权限规则配置、数据模型设计、查询语句的写法、认证角色的差异,任何一个环节出问题,前端表现都是莫名其妙的报错或者数据缺失。
我之前那个项目就是典型例子。Nuxt.js 是 Vue 生态里做 SSR 和全栈应用非常顺手的框架,配合 Nhost 提供的 GraphQL 端点,理论上可以做到前端自己把数据请求、缓存、渲染全包圆。但实际开发中你会发现,Nhost 的 GraphQL API 是 Hasura 引擎驱动的,它有一套基于角色的权限体系,同一个接口,不同角色拿到的字段可能完全不同。如果没有针对性地测试,等部署到生产环境再发现“管理员能看的数据用户也能看”或者“写入时字段被静默丢弃”,那就相当被动了。
所以我把整套测试方法压缩成 3 步,每一步都有明确目标:
- 第一步,搭一套干净、隔离的测试环境,保证测试数据不会污染开发数据;
- 第二步,设计覆盖正向、边界、异常、权限四个维度的查询和变更用例;
- 第三步,把测试接入 Nuxt.js 项目的自动化流程,让接口可靠性变成可回归的工程能力。
这三步看起来简单,但每一步都有不少细节,尤其是数据隔离和权限用例这两块,我估计能劝退 80% 想偷懒的人。下面逐个展开。
2. 第一步:搭一个“不脏”的测试环境(数据隔离是关键)
很多人测试 GraphQL 接口,习惯直接对着线上或者开发环境一顿操作。短期看是省事,但隐患很大:线上数据会被测试数据污染,开发环境的数据会被反复创建删除搞得一团乱,而且权限规则一旦改了,之前的测试结果就完全不可信。我的建议是,从一开始就用 Nhost 的本地开发环境配合专用测试数据集,这是“零失败”的基石。
2.1 用 Nhost CLI 拉起完整本地环境
Nhost 官方提供了 CLI 工具,一条命令就能把整套本地依赖拉起来。具体操作是:
npm install -g nhost nhost init my-test-project cd my-test-project nhost devnhost dev会启动 Docker 容器,把 Postgres、Hasura、Auth、Storage 全部跑起来,默认地址是http://localhost:1337,GraphQL 端点就是http://localhost:1337/v1/graphql。这里有个经验:本地环境跑起来之后,先确认nhost status显示所有服务都是 healthy,不然后面测试会莫明其妙失败。
然后需要在项目根目录创建.env文件,把连接信息放进去。以 Nuxt.js 项目为例,我通常放在.env.test,避免和开发环境混在一起:
NHOST_URL=http://localhost:1337 NHOST_GRAPHQL_URL=http://localhost:1337/v1/graphql NHOST_ADMIN_SECRET=test-admin-secret这里注意,Nhost 本地环境的 admin secret 默认是nhost-admin-secret,但你完全可以在nhost dev启动后通过config.yaml改成自己熟悉的。别用同一个 secret 跑测试和生产,这是基本的安全意识,同时也是数据隔离的一部分。
2.2 为什么测试数据必须隔离
我见过有人直接在开发库上测接口,结果一个delete_users(where: {})下去,开发环境里所有测试账号全没了,连同真实用户的关联数据也被级联删除。那种事故不需要发生一次,你就知道隔离有多重要。
真正做得比较顺的方式是:为测试单独建一个 schema 或者单独一套表,每次跑测试前做一次迁移和种子数据填充,跑完后清空。Nhost 的本地环境本质上就是一个独立的 Postgres,你可以把测试用的表、视图、函数都定义清楚。我习惯在nhost/migrations目录下维护测试专用的 seed 迁移,例如创建一批固定 ID 的测试用户和测试文章:
INSERT INTO auth.users (id, email, display_name, role) VALUES ('00000000-0000-0000-0000-000000000001', 'test-user@example.com', 'Test User', 'user'), ('00000000-0000-0000-0000-000000000002', 'test-admin@example.com', 'Test Admin', 'admin');然后写一个reset-test-data.sh脚本来重建这套数据。这样每次测试的起点都是完全一致的,可重复性非常高。我个人强烈不建议在测试代码里直接删库重建,因为 Nhost 的 auth schema 和 Hasura metadata 有依赖关系,删错了目录反而会把手动配的权限规则也删掉。
2.3 用真实 HTTP 请求验证环境是否就绪
环境搭建完之后,先用一个最简单的 GraphQL query 验证连通性。我一般用curl快速验证,再进 Nuxt.js 里做正式测试:
curl -X POST http://localhost:1337/v1/graphql \ -H "Content-Type: application/json" \ -H "x-hasura-admin-secret: test-admin-secret" \ -d '{"query":"{ __typename }"}'如果返回{"data":{"__typename":"Query"}},说明环境是通的。这一步看着简单,但能帮你区分“测试代码问题”和“环境问题”,我在实际项目里靠这个小动作省了太多时间。
2.4 常见环境坑位
本地环境跑 Nhost 时,最常遇到的有三类问题:
- Docker 资源不足:Nhost 本地要跑 Postgres、Hasura、Auth 等多个容器,内存小于 4G 的机器会频繁 OOM,测试跑到一半突然超时,排查起来很痛苦。建议 Docker Desktop 的内存配额至少给到 6G。
- 端口冲突:默认 1337 端口被占用时,
nhost dev会自动换端口,但 Nuxt.js 的.env不会自动更新,导致测试连不上环境。最好先把占用端口的进程关掉,或者固定端口。 - metadata 不同步:你在 Nhost 控制台里改了权限规则,但本地环境没有同步最新的 Hasura metadata,测试结果就不准。跑测试前执行一次
nhost metadata reload,比什么都强。
环境稳定之后,第二步才是真正考验测试设计能力的部分。
3. 第二步:查询和变更用例怎么设计才算有效
GraphQL 接口测试和普通 REST 接口测试有一个本质区别:GraphQL 是声明式的,客户端指定查什么,服务端就返回什么。所以测试用例不能只盯着“接口通不通”,而要覆盖查询的字段组合、过滤条件、分页参数、错误处理,以及最重要的——权限边界。我在实战中把用例分成四类,每一类都有它的典型坑。
3.1 正常路径用例:验证核心数据返回
正常路径是测试的起点,目标就是保证核心业务场景下接口能返回预期数据。以 Nuxt.js 博客项目为例,最基础的查询:
query GetPost($id: uuid!) { post_by_pk(id: $id) { id title content author { display_name } } }对应的 Vitest 测试用例:
import { describe, it, expect } from 'vitest'; import { request } from 'graphql-request'; const graphqlEndpoint = process.env.NHOST_GRAPHQL_URL!; const adminSecret = process.env.NHOST_ADMIN_SECRET!; describe('post 查询正常路径', () => { it('根据主键返回文章及作者信息', async () => { const query = ` query GetPost($id: uuid!) { post_by_pk(id: $id) { id title content author { display_name } } } `; const data: any = await request( graphqlEndpoint, query, { id: '00000000-0000-0000-0000-000000000010' }, { 'x-hasura-admin-secret': adminSecret } ); expect(data.post_by_pk.title).toBe('测试文章标题'); expect(data.post_by_pk.author.display_name).toBe('Test User'); }); });正常路径用例的意义在于锁定行为基线。一旦权限规则或者数据模型变动,这个用例能第一时间暴露变化。
3.2 边界用例:分页、过滤、可选参数
GraphQL 查询特别容易在分页和过滤条件上出幺蛾子。Nhost 底层的 Hasura 支持where、limit、offset、order_by,参数组合多了,接口行为就容易超出预期。
我举一个实战踩过的例子:某个列表接口需要支持关键词搜索,前端用where: { title: { _ilike: "%关键词%" } },看起来没问题,但测试中传空字符串时,Hasura 会把_ilike: "%%"当成匹配所有。于是搜索框什么都不输入时,列表居然返回全部数据,而产品需求是此时返回空列表。这类边界问题,不写测试根本发现不了。
所以边界用例至少要覆盖这几类输入:
- 空字符串、空数组、null 值;
- 分页
limit: 0、limit: 1000、超过实际数据量的 offset; - 排序字段传不存在的列名;
- 过滤条件组合过长。
每个边界用例都要明确预期行为,而不是只断言“不报错”。
3.3 异常路径用例:错误应当被正确处理
异常路径测试是很多人偷懒的地方,它的价值在于让你知道接口在失败时的表现。我通常专门测四类错误:
| 错误类型 | 示例 | 预期行为 |
|---|---|---|
| 数据不存在 | 查询不存在的 ID | 返回null或明确错误码 |
| 参数非法 | 不符合 uuid 格式的 ID | 返回 GraphQL 错误,而不是 500 |
| 权限不足 | 普通用户查管理员字段 | 返回权限错误,字段级缺失 |
| 唯一约束冲突 | 重复插入同一邮箱 | 明确提示唯一键冲突 |
以 Nuxt.js 前端为例,如果你在useAsyncData里请求一个不存在的文章,GraphQL 可能返回data: null+errors数组,而不是直接抛异常。前端如果没有处理这段errors,页面会白屏或者一直转圈。测试里我会专门写一个用例:
it('查询不存在的文章时,返回错误而非静默成功', async () => { const query = ` query GetPost($id: uuid!) { post_by_pk(id: $id) { id title } } `; try { await request(graphqlEndpoint, query, { id: '99999999-9999-4999-9999-999999999999' }, headers); } catch (err: any) { expect(err.response.errors).toBeDefined(); return; } throw new Error('应该抛出 GraphQL 错误'); });接口测试如果只测“正常路径”,那仅仅是验证了平台没挂而已,远谈不上可靠性。
3.4 权限用例:角色差异才是 Nhost 测试的重头戏
Nhost 的权限模型是角色驱动的,默认有user、anonymous、admin等角色,你也可以自定义角色。同一个查询,不同角色看到的字段和数据范围完全不同。权限用例的核心,就是验证最小权限原则是否落地。
我在项目里最喜欢用的办法是建立一张“角色-访问矩阵”:
| 角色 | 可读字段 | 可写操作 | 可访问数据范围 |
|---|---|---|---|
| anonymous | 文章标题、摘要 | 无 | 仅 published 文章 |
| user | 全部文章字段 | 创建自己的文章、更新自己的文章 | 仅自己的数据或公开数据 |
| admin | 全部字段含软删除标记 | 任意增删改 | 全局 |
然后在测试里,用x-hasura-role: user或者带 JWT 的Authorization头去请求同一个查询,断言返回字段和错误信息都符合预期。这个环节最容易发现问题,比如:
- 用户竟然能读到其他用户的手机号;
user角色通过直接指定id更新了别人的文章;anonymous角色依然能查_ilike搜索到未发布的文章。
这些坑,一旦线上暴露,就不是“接口不可靠”的问题了,而是安全事故。所以权限用例宁可多写,不可少写。
3.5 变更操作的用例不只是“能写入”
很多人测变更只测“插入成功没”,但真实业务里变更往往带有关联逻辑。Nhost 的 Hasura 支持触发器、存储过程、外键约束,这些都会影响变更的结果。我之前遇到一个需求:用户创建订单时,系统要自动更新订单总数。直接在 Nhost 的 SQL 里写了触发器,但测试时发现通过 GraphQL 插入订单后,总数没有变化。最后排查发现触发器绑定的是原表,而 GraphQL 走了视图,事件根本没触发。
所以变更用例至少要覆盖:
- 插入正常数据后,关联数据是否正确变化;
- 更新操作是否遵守权限规则,尤其是否只能更新自己的数据;
- 删除操作是否正确处理外键依赖,是级联删除还是会拒绝;
- 重复操作是否幂等,比如重复点击“点赞”按钮会不会产生多条记录。
写完用例之后,进入自动化阶段,这才算把测试从“一次性验证”升级成“持续保障”。
4. 第三步:把测试塞进 Nuxt.js 工程和 CI 流程
前两步解决的是“测什么”的问题,第三步解决的是“怎么让测试持续跑起来”的问题。手工跑测试永远不可靠,尤其当团队多人协作时,任何人改动数据模型或权限规则,都可能引发接口行为变化。如果每次变化都依赖人工去跑一遍用例,那测试就形同虚设。所以我把测试直接集成进 Nuxt.js 工程的开发流程和 CI 管道。
4.1 选择测试工具:Vitest 是 Nuxt.js 项目的天然搭档
Nuxt.js 3 基于 Vite 构建,测试框架我用 Vitest 配@nuxt/test-utils,体验比 Jest 顺滑很多。Vitest 原生支持 ESM、TypeScript、vi.mock,和 Nuxt 的模块体系配合得很好。初始化一个测试工程:
npm install -D vitest @nuxt/test-utils graphql-request然后在vitest.config.ts里配置测试环境变量:
import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { env: { NHOST_GRAPHQL_URL: 'http://localhost:1337/v1/graphql', NHOST_ADMIN_SECRET: 'test-admin-secret', NHOST_USER_EMAIL: 'test-user@example.com', NHOST_USER_PASSWORD: 'test-password-123', }, setupFiles: ['./tests/setup.ts'], include: ['tests/**/*.spec.ts'], hookTimeout: 30000, }, });这里有个小坑:Vitest 的环境变量配置只对测试进程生效,而process.env在 Nuxt 的 runtime 里会被 Nuxi 的加载顺序影响。所以测试代码里涉及环境变量的地方,我都统一从一个src/config/test-config.ts模块读取,而不是散落各处:
export const testConfig = { graphqlUrl: process.env.NHOST_GRAPHQL_URL ?? 'http://localhost:1337/v1/graphql', adminSecret: process.env.NHOST_ADMIN_SECRET ?? '', userEmail: process.env.NHOST_USER_EMAIL ?? '', userPassword: process.env.NHOST_USER_PASSWORD ?? '', };4.2 用 JWT 模拟不同角色,而不是只依赖 admin secret
前面提到权限测试要验证角色差异,自动化实现时就不能全都用 admin secret 打接口,因为那样测不出权限。正确做法是先通过 Nhost Auth 接口登录获取 JWT,再带着Authorization: Bearer <token>请求 GraphQL。
我用一个登录辅助函数:
import { request } from 'graphql-request'; export async function getAuthToken(email: string, password: string): Promise<string> { const authEndpoint = process.env.NHOST_URL + '/v1/auth/signin/email-password'; const res: any = await request(authEndpoint, { email, password, }); return res.session.accessToken; }然后在测试里分别获取 user 角色和管理员角色的 token:
const userToken = await getAuthToken(testConfig.userEmail, testConfig.userPassword); const adminToken = await getAuthToken(testConfig.adminEmail, testConfig.adminPassword);用 headers 传递:
const userHeaders = { Authorization: `Bearer ${userToken}` }; const adminHeaders = { 'x-hasura-admin-secret': testConfig.adminSecret };要注意 Nhost 的 auth endpoint 返回结构在不同版本里略有差异,有的是session.accessToken,有的是accessToken,写辅助函数之前先看本地环境的实际返回,不然容易卡在这一步。我的经验是直接用console.log打印一次返回结构,再写类型定义。
4.3 测试文件组织:一个业务模块一个测试套件
我在 Nuxt.js 项目里习惯按业务模块组织测试文件,而不是把所有 GraphQL 测试堆在一个文件里:
tests/ setup.ts auth.spec.ts posts.spec.ts comments.spec.ts profiles.spec.ts每个 spec 文件都约定好四个describe块:正常路径、边界条件、异常处理、权限规则。这样一个大模块下来,光 posts 模块我就能写出 30 多个用例,跑起来也很快,因为 GraphQL 请求本身是轻量级的,瓶颈只在网络和环境 IO。
下面是posts.spec.ts的一个完整示例片段:
import { describe, it, expect, beforeAll } from 'vitest'; import { request } from 'graphql-request'; import { testConfig } from '../src/config/test-config'; import { getAuthToken } from './helpers/auth'; let userToken = ''; let headers = {}; let createdPostId = ''; beforeAll(async () => { userToken = await getAuthToken(testConfig.userEmail, testConfig.userPassword); headers = { Authorization: `Bearer ${userToken}` }; }); describe('posts 模块权限控制', () => { it('用户只能读取已发布文章', async () => { const query = ` query ListPosts($where: post_bool_exp) { posts: post(where: $where, order_by: { created_at: desc }) { id title status } } `; const data: any = await request( testConfig.graphqlUrl, query, { where: { status: { _nin: ['draft'] } } }, headers ); data.posts.forEach((p: any) => { expect(['published', 'archived']).toContain(p.status); }); }); it('用户尝试修改他人文章时,接口返回权限错误', async () => { const mutation = ` mutation UpdatePost($id: uuid!, $title: String!) { update_post_by_pk(pk_columns: { id: $id }, _set: { title: $title }) { id } } `; try { await request( testConfig.graphqlUrl, mutation, { id: '00000000-0000-0000-0000-000000000099', title: 'hack' }, headers ); } catch (err: any) { expect(err.response.errors).toBeDefined(); return; } throw new Error('非法修改未被执行权限拦截'); }); });跑测试的命令很简单:
npm run test如果需要在跑测试前自动启动 Nhost 本地环境,可以在package.json里配一个组合脚本:
{ "scripts": { "test:api": "nhost dev & vitest run && nhost down" } }4.4 接入 GitHub Actions 做持续回归
本地跑测试只能约束自己,真正让接口可靠性有保障,必须靠 CI 自动跑。以 GitHub Actions 为例,我常用的 workflow 文件长这样:
name: GraphQL API Tests on: push: branches: [main] pull_request: branches: [main] jobs: nhost-api-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: nhost dev --no-tty - run: npm run test:api这里有一个关键的实操经验:nhost dev --no-tty是为了在 CI 的非交互环境下启动 Nhost,不加这个参数的话,CLI 会等待 TTY 输入,CI 步骤会一直卡住。然后等 Nhost 服务就绪需要一点时间,我会在脚本里加一个健康检查循环:
#!/usr/bin/env bash for i in {1..30}; do curl -s -X POST http://localhost:1337/v1/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{ __typename }"}' && break sleep 2 done等到 GraphQL 端点可访问后再跑真正的测试套件,否则第一次 CI 跑肯定因为环境没起来而失败。
CI 里还需要注意:不要用同一个 Nhost 项目跑开发和测试,否则并发 PR 之间会互相污染数据。我建议 CI 里也走完全独立的 Nhost 项目或本地隔离环境,数据不干净,测试结果就不值得信任。
4.5 覆盖率不是目标,关键路径才是
有些团队喜欢追求 100% 覆盖率。以我的经验,GraphQL 接口测试的覆盖率意义有限,因为很多代码路径是平台自动生成的,覆盖率高不代表覆盖到了权限边界和异常处理。我更看重的是一份测试“关键路径清单”:
- 所有对外暴露的查询和变更;
- 每个角色至少一条正常路径用例;
- 每个角色至少一条越权用例;
- 每类关键实体的分页和过滤用例;
- 每条重要外键关联的副作用用例。
把这份清单编进团队的测试规范里,比喊“覆盖率 90%”口号有价值得多。
5. 常见问题与排查实录
测试跑起来之后,你大概率会遇到一批非常典型的坑。我把这些年在 Nhost GraphQL 测试上遇到的真实问题和排查思路整理成表格,这比任何理论都实用:
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 测试请求返回 401 | JWT 过期或 secret 不匹配 | 打印 JWT 解码看角色;重置本地环境变量 |
查询返回data: null且无错误信息 | Hasura metadata 未同步 | 执行nhost metadata reload |
| 同一个查询,本地正常但 CI 失败 | CI 环境的 Nhost 未就绪 | 加健康检查循环,等待端点就绪 |
| 权限用例不生效,admin secret 也能通过 | 测试代码误用了 admin secret 而非用户 token | 检查 headers 传参是否正确 |
| 字段静默缺失(没有错误) | Hasura 权限列裁剪 | 用 admin 身份跑一次同一查询,对比返回结构 |
| 删除测试数据后关联数据被连带删除 | 外键设置了 CASCADE | 单独设计用例验证删除行为 |
| 测试环境端口冲突导致连接拒绝 | 本地 1337 端口被占用 | 清理 Docker 容器或固定端口 |
5.1 JWT 过期问题怎么处理
Nhost Auth 默认签发的 access token 有效期是 15 分钟。如果你的测试套件跑得久,或者用例之间间隔时间太长,就会遇到“前面用例通过、后面用例突然 401”的诡异情况。解决办法有两个:
第一,在beforeAll里登录一次拿 token,所有用例复用同一个 token,跑完就结束,不涉及长期存活问题;第二,如果测试要持续运行,就定期用 refresh token 刷新 access token。我推荐前者,因为 GraphQL 测试通常几秒内就跑完,一个 token 完全够用。
5.2 权限测试里“字段静默缺失”是最隐蔽的
Hasura 在角色权限里可以限制返回列。比如user角色被配置成只能读id、title,当你查{ id title content }时,接口不会报错,而是把content字段直接忽略,返回结果里根本没有这个 key。这在普通业务测试里很可能被忽略,因为只要接口不报错,前端就可能觉得“一切正常”,但实际数据已经缺了一整块。
我的排查技巧是:先用 admin secret 跑一次同样的查询,拿到“完整字段”的基线返回,再用受限角色 token 跑一次,对比两个 JSON 的 key 差异。差异一目了然,不用瞎猜。这个技巧救过我很多次,属于典型的“经验比文档值钱”的时刻。
5.3 不要用 delete 全表的方式做测试清理
我早期喜欢在每个用例结束后执行delete_post(where: {}),简单粗暴。但后来遇到一个惨痛教训:某次清理操作在 CI 环境里先于权限规则更新执行,结果测试数据没清掉,下一轮用例全部因为唯一约束冲突失败。从那以后,我的清理策略改成“定向清理”,只删除本测试会话里创建的数据:
mutation Cleanup($postIds: [uuid!]!) { delete_post(where: { id: { _in: $postIds } }) { affected_rows } }每个用例在创建测试数据时,就把 ID 记录到一个createdIds列表,afterAll里统一清理。这样即使某条用例挂了,清理也不会误伤其他数据。
5.4 网络层缓存导致的“假失败”
Nuxt.js 开发环境有时会对 GraphQL 请求做内存级缓存,测试代码如果在 Nuxt 的 nitro 进程内直接调用接口,可能命中缓存,看到的是旧数据。这不是 Nhost 的问题,而是测试代码和 Nuxt 共享了上下文导致的。我一般在tests/目录里写原生 Node 请求逻辑,不经过 Nuxt 的 runtime 上下文,只为确保测试走的是真实 GraphQL 端点。
遇到“改了数据但查询结果没变”的情况,先想想是不是这个原因。排查方法是把请求改成带一个随机参数的别名,比如post2: post_by_pk(id: $id),彻底跳过查询结果缓存。
最后再分享一个实际经验
时隔大半年再看我之前那个项目,最大的体会是:接口测试不是写完就完了,它是一个需要持续维护的活。每次 Nhost 升级、每次权限规则调整、每次数据模型加字段,都可能让历史用例失效或者出现新的边界问题。我的习惯是给测试文件加一个CHANGELOG.md,每次改动测试时记一笔,比如“给 user 角色增加了手机号读取权限,同步新增匿名角色越权用例”。这样团队其他成员看到测试文件的变动历史,能快速理解测试策略演进的原因。
另外还有一个实用小技巧:在package.json里挂一个test:watch脚本,配合 Vitest 的 watch 模式,开发时改一行权限规则,测试立刻自动重跑,反馈速度非常快。这个习惯让我的迭代节奏明显变快了,因为规则改完有没有破坏下游逻辑,几秒钟就能看到结果。希望这套 3 步打法能帮到正在折腾 Nhost 和 Nuxt.js 的你,少踩几个我踩过的坑。