Scalar Fastify 集成实战:用 @scalar/fastify-api-reference 从 OpenAPI 文档一键渲染交互式 API Reference
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
本文基于 Scalar 仓库中的 Fastify 集成文档 与对应插件源码 integrations/fastify/src/fastifyApiReference.ts 撰写。它介绍如何通过@scalar/fastify-api-reference插件,在你的 Fastify 应用中以几行代码渲染出基于 OpenAPI/Swagger 文档的交互式 API Reference,并深入讲清文档来源优先级、OpenAPI 端点再暴露、CSP nonce、日志与鉴权钩子等每一项插件选项的底层实现,帮助你既会配置,也懂其原理。
安装与注册
@scalar/fastify-api-reference是一个标准的 Fastify 插件(源码中通过fastify-plugin包装,见 fastifyApiReference.ts),安装后直接register即可:
npm install @scalar/fastify-api-referenceawait fastify.register(import('@scalar/fastify-api-reference'), { routePrefix: '/reference', })注意:该插件同时兼容 Fastify v4 与 v5,并且以 ES Module 形式发布,因此需要使用动态
import()(如上所示)或在 ESM 项目中通过顶层import注册。从源码的fastify-plugin元信息看,其声明的兼容范围为fastify: '4.x || 5.x || 6.x',Fastify 会在注册时校验该范围并在不兼容时快速失败(见 fastifyApiReference.ts#L262-L267)。当前版本要求 Node.js>=20(见 package.json)。
routePrefix决定了 API Reference 的访问路径,默认值为/reference。源码中getRoutePrefix会去掉尾部的/,统一处理带不带斜杠的写法(见 fastifyApiReference.ts#L31-L36)。
文档来源:content、url 与 @fastify/swagger 三级优先级
如果你已有 OpenAPI/Swagger 文档,可以直接把 URL 传给插件:
// Render an API reference for a given OpenAPI/Swagger spec URL fastify.register(import('@scalar/fastify-api-reference'), { routePrefix: '/reference', configuration: { title: 'Our API Reference', url: '/openapi.json', }, })如果项目中已经注册了@fastify/swagger,插件会自动从其生成的fastify.swagger()中取文档,此时什么都不用配:
await fastify.register(import('@scalar/fastify-api-reference'), { routePrefix: '/reference', })这里的优先级在源码中有明确实现:插件在注册时依次探测configuration.content→configuration.url→@fastify/swagger(见 fastifyApiReference.ts#L76-L105):
configuration.content:直接传入 OpenAPI 文档对象;值得注意的是content也支持传入一个函数(返回文档对象),源码会按需调用它取值。这允许你在请求时动态构造 spec(例如按租户返回不同文档)。测试用例 fastifyApiReference.test.ts 中对content: spec与content: () => spec两种形式都做了覆盖。configuration.url:Reference 页面会在浏览器端直接加载该 URL(同源或跨域均可)。@fastify/swagger:通过fastify.hasPlugin('@fastify/swagger')判断是否注册,且要求fastify.swagger是函数(若@fastify/swagger使用了decorator选项导致该函数不可用,则会跳过)。
如果三者都未提供且没有配置sources,插件不会报错,而是打出一条警告后正常退出:
[@scalar/fastify-api-reference] You didn't provide a `content`, `url`, `sources` or @fastify/swagger could not be found. Please provide one of these options.这一行为同样有测试用例保障(见 fastifyApiReference.test.ts#L496-L514)。
插件接收的configuration是 Scalar 的通用配置对象,完整字段说明见 configuration。
插件选项(Options)一览
插件接受以下选项(类型定义见 integrations/fastify/src/types.ts):
| Option | Type | Default | Description |
|---|---|---|---|
routePrefix | string | /reference | API Reference 的访问路径前缀。 |
configuration | object | – | Scalar 通用配置对象(url、content、sources、title、theme、layout等,完整见 configuration)。 |
openApiDocumentEndpoints | object | { json: '/openapi.json', yaml: '/openapi.yaml' } | OpenAPI 文档再暴露的端点路径,相对于routePrefix。 |
hooks | object | – | 作用于插件注册的所有路由的 FastifyonRequest与preHandler钩子(常用于鉴权)。 |
logLevel | string | – | 插件路由的日志级别,取值'fatal' \| 'error' \| 'warn' \| 'info' \| 'debug' \| 'trace' \| 'silent',设为silent可静默这些路由的日志。 |
再暴露 OpenAPI 文档端点
当你通过configuration.content或@fastify/swagger提供文档时,插件会在routePrefix下把解析后的文档重新暴露出来,方便其他工具消费:
/reference/openapi.json/reference/openapi.yaml
文档会先经过@scalar/openapi-parser的normalize归一化,再分别用toJson/toYaml输出;响应同时带有Content-Disposition: filename=<slug>.json|.yaml(文件名取自 spec 标题经 slug 化处理,见 fastifyApiReference.ts#L130-L179),并附带宽松的 CORS 头Access-Control-Allow-Origin: *与Access-Control-Allow-Methods: *,外部工具可以直接抓取。CORS 行为由测试明确断言(见 fastifyApiReference.test.ts#L269-L293)。
两个重要的实现细节:
configuration.url来源不会再暴露。因为此时浏览器直接加载你自己的 URL,本地再暴露一份没有意义;测试也验证了该场景下/reference/openapi.json与/reference/openapi.yaml返回 404(见 fastifyApiReference.test.ts#L541-L565)。- 只有 HTML 页面内的 spec 引用是相对路径(
./openapi.json),以便在反向代理下正常工作;而文档端点本身只在content/swagger来源下注册。
端点路径可以用openApiDocumentEndpoints修改:
await fastify.register(import('@scalar/fastify-api-reference'), { routePrefix: '/reference', openApiDocumentEndpoints: { json: '/openapi.json', yaml: '/openapi.yaml', }, })Content Security Policy(CSP)与 nonce
插件把 Scalar 的 standalone bundle 从你自己的源上提供服务(路径为${routePrefix}/js/scalar.js),因此无需在 CSP 中放行 CDN ——script-src 'self'即可覆盖。
若要在不含script-src 'unsafe-inline'(也不含'unsafe-eval')的严格 CSP 下运行,传入一个nonce即可。它会作用于插件输出的内联<script>与<style>标签,同时通过<meta property="csp-nonce">暴露给 bundle,使其在运行时注入的样式表也携带同一 nonce(渲染逻辑见 packages/client-side-rendering/src/html-rendering.ts#L132-L160):
await fastify.register(import('@scalar/fastify-api-reference'), { routePrefix: '/reference', configuration: { nonce: 'r4nd0m-nonce-value', }, })与之匹配的响应头策略形如:
Content-Security-Policy: script-src 'self' 'nonce-r4nd0m-nonce-value'; style-src 'self' 'unsafe-inline'注意:
style-src仍需要'unsafe-inline'。Reference 会渲染内联style="..."属性,而 nonce 无法授权这类属性——这是 CSP 规范本身的限制,不是插件的问题。
注意:
nonce在插件注册时被读取一次,之后每个响应都发送同一个值;目前不支持按请求生成 nonce。
从渲染源码看,当设置了nonce时,页面默认回退到单文件 UMD bundle(因为 ESM 构建通过原生import拉取的分块脚本无法携带 nonce,见 html-rendering.ts#L214-L267)。此外,根据使用场景可能还需要扩展其他指令,例如connect-src(让内置 API Client 能访问你的 API)、img-src/font-src(文档或主题引用的图片与字体)。
主题(Themes)
默认使用为 Fastify 场景定制的内置主题。你也可以在configuration中指定其他主题(全部主题见 themes):
await fastify.register(import('@scalar/fastify-api-reference'), { routePrefix: '/reference', configuration: { theme: 'purple', }, })日志(logLevel)
插件与 Fastify 的 logger 完全兼容。通过logLevel可以单独控制插件注册的路由的日志级别:
fastify.register(import('@scalar/fastify-api-reference'), { routePrefix: '/reference', logLevel: 'silent', })源码中logLevel会被展开到插件注册的每一条路由上(HTML 页面、JS 文件、JSON/YAML 文档端点)。测试用例通过自定义 logger serializer 断言:当logLevel: 'silent'时,对上述所有路由发起请求后不会产生任何请求日志(见 fastifyApiReference.test.ts#L457-L494)。
鉴权:用 hooks 保护 API Reference
由于插件注册的是标准 Fastify 路由,你可以通过hooks选项套用任何 Fastify 鉴权插件。下面以@fastify/basic-auth为例:
import FastifyBasicAuth from '@fastify/basic-auth' await fastify.register(FastifyBasicAuth, { validate(username, password, request, reply, done) { if (username === 'admin' && password === 'admin') { done() } else { done(new Error('Access denied')) } }, authenticate: true, }) await fastify.register(import('@scalar/fastify-api-reference'), { routePrefix: '/reference', hooks: { onRequest: fastify.basicAuth, }, })onRequest与preHandler会应用到插件注册的所有路由——HTML 页面、打包的 JS 文件、OpenAPI 文档端点,一个都不漏。这一行为有直接的测试验证:未带Authorization头时/reference与/reference/js/scalar.js均返回 401,带上正确凭据后均返回 200(见 fastifyApiReference.test.ts#L403-L455)。
完整实战:从零搭一个带 API Reference 的 Fastify 项目
第一步:创建 Fastify 项目(可选)
从零开始的话,先安装 Fastify:
npm init npm install fastify然后创建index.js:
// index.js import Fastify from 'fastify' // Instantiate the framework const fastify = Fastify({ logger: true, }) // Declare a route fastify.get('/', function (request, reply) { reply.send({ hello: 'world' }) }) // Run the server fastify.listen({ port: 3000 }, function (err, address) { if (err) { fastify.log.error(err) process.exit(1) } console.log(`Fastify is now listening on ${address}`) })由于这里使用了 ES Module 的import语法,直接运行会失败,需要在package.json中补充:
{ "name": "my-fastify-app", "version": "1.0.0", "description": "", "main": "index.js", + "type": "module", "scripts": { + "dev": "npx nodemon index.js", "test": "echo \"Error: no test specified\" && exit 1" }, … }其中"type": "module"是让 Node 支持 ES Module 的关键;dev脚本只是图方便(用 nodemon 热重启)。运行:
npm run dev输出中会打印服务地址http://localhost:3000,浏览器打开即可看到示例 JSON。
第二步:接入 @fastify/swagger(可选)
别被 "Swagger" 这个名字误导——这是官方的 OpenAPI 3.0 生成包。安装:
npm install @fastify/swagger然后替换index.js的内容:
import FastifySwagger from '@fastify/swagger' import Fastify from 'fastify' // Instantiate the framework const fastify = Fastify({ logger: true, }) // Set up @fastify/swagger await fastify.register(FastifySwagger, { openapi: { info: { title: 'My Fastify App', version: '1.0.0', }, components: { securitySchemes: { apiKey: { type: 'apiKey', name: 'apiKey', in: 'header', }, }, }, }, }) fastify.put( '/example-route/:id', { schema: { description: 'post some data', tags: ['user', 'code'], summary: 'qwerty', security: [{ apiKey: [] }], params: { type: 'object', properties: { id: { type: 'string', description: 'user id', }, }, }, body: { type: 'object', properties: { hello: { type: 'string' }, obj: { type: 'object', properties: { some: { type: 'string' }, }, }, }, }, response: { 201: { description: 'Successful response', type: 'object', properties: { hello: { type: 'string' }, }, }, default: { description: 'Default response', type: 'object', properties: { foo: { type: 'string' }, }, }, }, }, }, (req, reply) => { reply.code(201).send({ hello: `Hello ${req.body.hello}` }) }, ) // Serve an OpenAPI file fastify.get('/openapi.json', async (request, reply) => { return fastify.swagger() }) // Wait for Fastify await fastify.ready() // Run the server fastify.listen({ port: 3000 }, function (err, address) { if (err) { fastify.log.error(err) process.exit(1) } console.log(`Fastify is now listening on ${address}`) })这段代码的 TL;DR:1)注册 Swagger 插件;2)定义 API 名称等全局信息;3)为带schema的路由生成 OpenAPI 定义;4)暴露一个返回生成文档的 JSON 端点。重启服务后,http://localhost:3000/openapi.json应该能返回一份像样的 OpenAPI 文件。
仓库内的 playground 就是一个更完整的可运行示例:注册@fastify/swagger、定义带schema的 PUT/POST/GET/DELETE 四个路由,最后register(Scalar, { routePrefix: '/' }),默认监听 5053 端口。
第三步:用 Scalar 渲染 API Reference
npm install @scalar/fastify-api-reference在index.js中、await fastify.ready()之前加入:
// … // Render the API reference import ScalarApiReference from '@scalar/fastify-api-reference' await fastify.register(ScalarApiReference, { routePrefix: '/reference', // Additional hooks for the API reference routes. You can provide the onRequest and preHandler hooks hooks: { onRequest: function (request, reply, done) { done() }, preHandler: function (request, reply, done) { done() }, }, }) // …说明:
import语句放在文件中间在 ESM 中是合法的(会被提升),当然更规范的做法是移到文件顶部。
重启服务后访问http://localhost:3000/reference,就能看到交互式 API Reference。此后继续给 Fastify 添加带schema的路由,Reference 会随@fastify/swagger的生成结果自动保持同步。
第四步:自定义(可选)
把configuration对象传给插件即可定制大量细节(layout、theme、darkMode、customCss等,完整字段见 configuration;TypeScript 下所有选项都有自动补全):
import ScalarApiReference from '@scalar/fastify-api-reference' await fastify.register(ScalarApiReference, { routePrefix: '/reference', configuration: { layout: 'classic', // 更多配置项见 documentation/configuration.md }, })进阶:手工维护的 OpenAPI 文件
自动生成的 OpenAPI 很方便,但也有人偏好手工维护 spec。此时只需把url指向你已有的 OpenAPI 文件即可:
import ScalarApiReference from '@scalar/fastify-api-reference' await fastify.register(ScalarApiReference, { routePrefix: '/reference', configuration: { // On your domain: url: '/openapi.json', }, })注意:如果不使用
@fastify/swagger来生成并托管 spec,需要自己把它作为静态文件服务出去(例如使用@fastify/static)。
源码级实现要点
了解以下实现细节有助于排查代理、CSP 与构建问题:
- HTML 页面由
renderApiReference生成。插件的 HTML 路由调用@scalar/client-side-rendering的 renderApiReference 输出完整 HTML 文档:<div id="app"></div>加上加载 bundle 的<script>及序列化后的配置。配置序列化(serializeConfigToJs)会保留函数值(以toString()输出),保证回调类配置项能穿过内联<script>边界。 - bundle 自托管。
getJavaScriptFile从@scalar/api-reference读取 standalone 构建;在发布的 npm 包中,Vite 构建阶段会直接把脚本内联为字符串(见 getJavaScriptFile.ts),因此产物自包含、不再运行时读文件——把 Fastify 应用打包进 Docker 镜像时也不会因缺失构建产物而失效。 - 尾斜杠重定向。为了让页面内的 JS 引用(
js/scalar.js)保持相对路径,插件会把不带尾斜杠的/reference301 重定向到/reference/;若应用设置了ignoreTrailingSlash: true(兼容 Fastify 顶层配置与 v6 的routerOptions位置)则跳过该路由。带插件prefix前缀(如register(inner, { prefix: '/api' }))时的重定向也已被测试覆盖(见 fastifyApiReference.test.ts#L97-L141)。 - 对 @fastify/swagger 透明。插件为所有路由挂上
hide: true的 schema,即使@fastify/swagger已注册,这些路由也不会出现在生成的文档里(见 fastifyApiReference.ts#L17-L29);同时它不依赖@fastify/html等任何装饰器,测试中显式断言fastify.html保持未定义。
小结
@scalar/fastify-api-reference把"从 OpenAPI 到可交互 API Reference"压缩成一次register调用:文档来源支持content(对象或函数)、url与@fastify/swagger自动探测三级优先级;content/swagger来源下自动再暴露带 CORS 头的 JSON/YAML 端点;hooks、logLevel、CSPnonce、configuration通用配置则分别解决鉴权、日志、安全策略与外观定制。配合仓库内的 playground 示例 与 测试用例,可以快速验证每一个选项的实际行为。
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考