Scalar Fastify 集成实战:用 @scalar/fastify-api-reference 从 OpenAPI 文档一键渲染交互式 API Reference
2026/9/14 11:34:43 网站建设 项目流程

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-reference
await 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.contentconfiguration.url@fastify/swagger(见 fastifyApiReference.ts#L76-L105):

  1. configuration.content:直接传入 OpenAPI 文档对象;值得注意的是content也支持传入一个函数(返回文档对象),源码会按需调用它取值。这允许你在请求时动态构造 spec(例如按租户返回不同文档)。测试用例 fastifyApiReference.test.ts 中对content: speccontent: () => spec两种形式都做了覆盖。
  2. configuration.url:Reference 页面会在浏览器端直接加载该 URL(同源或跨域均可)。
  3. @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):

OptionTypeDefaultDescription
routePrefixstring/referenceAPI Reference 的访问路径前缀。
configurationobjectScalar 通用配置对象(urlcontentsourcestitlethemelayout等,完整见 configuration)。
openApiDocumentEndpointsobject{ json: '/openapi.json', yaml: '/openapi.yaml' }OpenAPI 文档再暴露的端点路径,相对于routePrefix
hooksobject作用于插件注册的所有路由的 FastifyonRequestpreHandler钩子(常用于鉴权)。
logLevelstring插件路由的日志级别,取值'fatal' \| 'error' \| 'warn' \| 'info' \| 'debug' \| 'trace' \| 'silent',设为silent可静默这些路由的日志。

再暴露 OpenAPI 文档端点

当你通过configuration.content@fastify/swagger提供文档时,插件会在routePrefix下把解析后的文档重新暴露出来,方便其他工具消费:

  • /reference/openapi.json
  • /reference/openapi.yaml

文档会先经过@scalar/openapi-parsernormalize归一化,再分别用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, }, })

onRequestpreHandler会应用到插件注册的所有路由——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对象传给插件即可定制大量细节(layoutthemedarkModecustomCss等,完整字段见 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 端点;hookslogLevel、CSPnonceconfiguration通用配置则分别解决鉴权、日志、安全策略与外观定制。配合仓库内的 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询