API Reference

openApiPlugin

Serve your OpenAPI document and an API reference UI for it, on any adapter.

Serve a reference UI for the OpenAPI document generated from your contract, and the document itself if you want it public. Declare it on new Kizuna(), pass openApiPluginServer() to server.api, and api.mount serves the routes.

pnpm add @ts-kizuna/openapi
bun add @ts-kizuna/openapi
npm install @ts-kizuna/openapi
import { openApiPlugin } from '@ts-kizuna/openapi';
import { openApiPluginServer } from '@ts-kizuna/openapi/server';

Parameters

openApiPlugin(props: OpenApiPluginProps): PluginDeclaration
openApiPluginServer(): PluginImplementation
ParameterTypeRequiredDescription
propsOpenApiPluginPropsYesEverything generateOpenApi takes, plus the options below

Props

OpenApiPluginProps extends GenerateOpenApiOptions, so info, servers, setOperationId and the rest work exactly as they do on generateOpenApi. On top of those:

OptionDefaultDescription
docsPathoffThe reference UI. Give it a path to serve it
jsonPathoffThe document. A path ending in .json
yamlPathoffThe document as YAML. A path ending in .yaml
provider'scalar'Which reference UI to render, 'scalar' or 'swagger'
cdnUrlthe provider's CDNWhere to load the UI's assets from
pageTitlethe document titleThe page's <title>
configurationnoneExtra config merged into the UI's initializer

Props are pure data, because they reach the contract and the contract reaches your client. The generator lives in openApiPluginServer(), which takes nothing: declaring the plugin is where it is configured.

They are also the options generateOpenApi uses when you call it without any, so a build step and the served document cannot disagree.

Each prop serves nothing until you give it a path. The document paths carry their extension in the type, so jsonPath: '/docs' does not compile.

Example

src/contract/k.ts
import { Kizuna } from '@ts-kizuna/core';
import { openApiPlugin } from '@ts-kizuna/openapi';

export const k = new Kizuna({
    tags,
    plugins: {
        openApi: openApiPlugin({
            info: {
                title: 'My API',
                version: '1.0.0',
            },
            setOperationId: true,
        }),
    },
});
src/server/api.ts
import { openApiPluginServer } from '@ts-kizuna/openapi/server';

export const api = server.api({
    router,
    plugins: {
        openApi: openApiPluginServer(),
    },
});
src/index.ts
api.mount(app);

Two halves

@ts-kizuna/openapi declares the paths it serves and the options the document is built from, as data on your contract. @ts-kizuna/openapi/server holds the generator that renders it. Miss the second half and server.api throws, naming the module to import. See Plugins.

Not in your client

Its routes are plugin routes, so they live outside contract.routes. Your fetch client and generated Swift and Kotlin clients never see them, and neither does the document itself.

Public by default

Nothing guards these routes. Gate them with your framework's own middleware if your document should not be public.

Air-gapped and strict CSP

cdnUrl points the page at a self-hosted copy of the UI's assets. For 'scalar' it is the script URL; for 'swagger' it is the directory holding swagger-ui.css and swagger-ui-bundle.js.

Serving it yourself

generateOpenApi is unchanged and exported, so you can skip the plugin and serve the document from your own route. Mixing works too: docsPath: false lets the plugin serve the document while you render your own UI, and leaving jsonPath unset keeps the plugin off a path you serve yourself.

See also

On this page