最近在使用 Fastify@fastify/swagger 为项目生成 OpenAPI 文档时,遇到一个奇怪的问题:明明每个路由都写好了 schema,但访问 /swagger-ui 时页面却提示 No operations defined in spec!,文档内容完全是空的。

发现问题

我按照官方文档的示例,先注册了 Swagger 插件,然后注册路由,启动服务。代码大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fastify = require('fastify')();

fastify.register(require('@fastify/swagger'), {
mode: 'dynamic',
openapi: { info: { title: 'My API', version: '1.0.0' } },
exposeRoute: true,
});

fastify.get('/users', {
schema: {
response: { 200: { type: 'array', items: { type: 'object' } } }
}
}, async () => [{ id: 1 }]);

fastify.listen({ port: 3000 });

但无论怎么调整配置,文档页面始终是空的,查原始JSON 中的 paths 对象也为 {}。我检查了插件顺序、schema 格式、路由注册时机,甚至写了一个最小化示例,问题依旧。

处理问题

我尝试了所有常见排查手段:

  1. 确认插件注册顺序 – 确保 Swagger 在路由之前注册。

  2. 检查 schema 格式 – 确认 response 字段完整,且 tagsdescription 等无缺失。

  3. 调整配置选项 – 补充 openapi.info 中的必要字段,开启 exposeRoute

  4. 查看原始 JSON – 访问 /swagger-ui/json,确认 paths 为空。

  5. 写一个简化版本 – 仅保留 Swagger 和一个简单接口,但结果依然失败。

这些步骤都没能解决问题,文档依然空空如也。

找到原因

在反复测试简化示例时,我忽然意识到:fastify.register 是一个异步方法,它会返回一个 Promise。如果我不等待它完成就直接注册路由,那么 Swagger 插件的初始化可能尚未结束,导致后续路由无法被正确捕获。

于是我将代码修改为:

1
2
await fastify.register(require('@fastify/swagger'), options);
fastify.get(...);

再次运行,文档终于正常显示了!原来正是缺少了 await 导致的问题。

总结

根本原因:
@fastify/swagger 插件在注册时需要进行一些异步初始化(如构建内部状态),如果注册后立即注册路由而不等待插件完成,这些路由的 schema 信息就无法被插件收集,最终生成的文档中 paths 为空。

解决方案:
在注册 Swagger 插件时,务必加上 await(或在回调中注册路由):

1
2
3
// 正确方式
await fastify.register(require('@fastify/swagger'), options);
fastify.get(...);

如果你使用了 fastify-plugin 或模块化路由,也要确保外层已经 await 了 Swagger 的注册。

预防建议:

  • 牢记 register 是异步的,尤其是在插件有初始化逻辑时。

  • 如果不想使用 await,可以在 register 的回调中注册路由,但这样会破坏代码的线性结构,推荐直接使用 await

  • 养成编写最小化测试用例的习惯,能更快定位问题。

希望这篇文章能帮助遇到同样问题的开发者少走弯路!