Skip to content

API Reference

Automatic interactive API documentation generated from your OpenAPI spec. A pluggable UI provider renders it - Scalar by default, or classic Swagger UI.

Renamed from SwaggerComponent

Swagger UI is one of the pluggable UI providers, not the only one, so the component carries a vendor-neutral name. The deprecated Swagger* aliases are removed: use ApiReferenceComponent, IApiReferenceOptions and ApiReferenceBindingKeys.API_REFERENCE_OPTIONS.

Quick Reference

ItemValue
Package@venizia/ignis
ClassApiReferenceComponent
UI FactoryUIProviderFactory
RuntimesBoth
ProviderValueWhen to use
Scalar'scalar'Modern, clean UI (default)
Swagger UI'swagger'Classic Swagger interface

Import Paths

typescript
import { ApiReferenceComponent, ApiReferenceBindingKeys, UIProviderFactory } from '@venizia/ignis';
import type { IApiReferenceOptions, IUIProvider, IUIConfig, IGetProviderParams } from '@venizia/ignis';

In one example

Register the component - no configuration required. The docs UI comes up at <app base path>/doc/explorer, the raw spec at <app base path>/doc/openapi.json. With the /api base path used throughout the getting-started guide, that's /api/doc/explorer.

typescript
// src/application.ts
import { ApiReferenceComponent, BaseApplication, ValueOrPromise } from '@venizia/ignis';

export class Application extends BaseApplication {
  preConfigure(): ValueOrPromise<void> {
    this.component(ApiReferenceComponent);
  }
}

Define routes with Zod schemas so they show up in the generated spec:

typescript
// src/controllers/hello.controller.ts
import { z } from '@hono/zod-openapi';
import { BaseRestController, controller, jsonContent, ValueOrPromise } from '@venizia/ignis';
import { HTTP } from '@venizia/ignis-helpers';

@controller({ path: '/hello' })
export class HelloController extends BaseRestController {
  constructor() {
    super({ scope: HelloController.name, path: '/hello' });
  }

  override binding(): ValueOrPromise<void> {
    this.defineRoute({
      configs: {
        path: '/',
        method: 'get',
        responses: {
          [HTTP.ResultCodes.RS_2.Ok]: jsonContent({
            description: 'A simple hello message',
            schema: z.object({ message: z.string() }),
          }),
        },
      },
      handler: (c) => {
        return c.json({ message: 'Hello, `IGNIS`!' }, HTTP.ResultCodes.RS_2.Ok);
      },
    });
  }
}

TIP

Only routes registered through defineRoute, bindRoute, or @api() with @hono/zod-openapi schemas appear in the generated spec.

How it works

  • Options merge group by group. binding() reads the bound IApiReferenceOptions, then shallow-merges base, doc, and ui each against their own defaults. Overriding ui.type alone still keeps ui.path and every base/doc field.
  • explorer.info is always overwritten. The component reads your package.json via application.getAppInfo(). It replaces explorer.info with { title, version, description, contact } - any explorer.info you bind is discarded. Edit package.json instead.
  • explorer.servers fills in only when empty. A supplied server entry is kept as-is. Otherwise, the component builds one from application.getServerAddress() plus the base path.
  • UI type resolution uses ??, not ||. The source is restOptions.ui.type ?? DocumentUITypes.SWAGGER. Only null/undefined falls back, and it falls back to 'swagger' - not the configured default 'scalar'.
    • An explicit empty string is NOT repaired by this fallback: it fails DocumentUITypes.isValid() and the component throws Invalid document UI Type immediately.
  • UI libraries load lazily. SwaggerUIProvider/ScalarUIProvider each await import() their rendering library inside render(). That happens on the first request to the docs UI, not at application startup. Only the configured provider's library is ever loaded.
  • ScalarUIProvider renames title to pageTitle. Scalar's own render API takes pageTitle, not title - worth knowing if you inspect the rendered output or write a custom UI provider.
  • Security schemes are always registered. JWT (bearer) and Basic security schemes are added to the OpenAPI registry unconditionally. Routes using authenticate: { strategies: ['jwt'] } or ['basic'] render the correct auth UI as a result.

Common tasks

Switch to Swagger UI

typescript
this.bind<IApiReferenceOptions>({
  key: ApiReferenceBindingKeys.API_REFERENCE_OPTIONS,
}).toValue({ restOptions: { ui: { type: 'swagger' } } });

Move the docs under a different base path

typescript
this.bind<IApiReferenceOptions>({
  key: ApiReferenceBindingKeys.API_REFERENCE_OPTIONS,
}).toValue({ restOptions: { base: { path: '/api-docs' } } });

Result: UI at <app base path>/api-docs/explorer, spec at <app base path>/api-docs/openapi.json. The group merge keeps doc.path/ui.path defaults.

Set the info block shown in the UI

explorer.info always comes from package.json. Update name, version, description, and author there - binding explorer.info directly has no effect.

Register a custom UI provider

UIProviderFactory.register() only understands 'swagger'/'scalar'. Register a custom provider directly on the factory before ApiReferenceComponent.binding() runs:

typescript
UIProviderFactory.getInstance().set('my-ui', new MyCustomUIProvider());

Reference

Options

typescript
export interface IApiReferenceOptions {
  restOptions?: {
    base?: { path?: string };
    doc?: { path?: string };
    ui?: { path?: string; type?: TDocumentUIType };
  };
  explorer?: {
    openapi?: string;
    info?: {
      title: string;
      version: string;
      description: string;
      contact?: { name: string; email: string };
    };
    servers?: Array<{ url: string; description?: string }>;
  };
  uiConfig?: Record<string, any>;
}
OptionTypeDefaultDescription
restOptions.base.pathstring'/doc'Base path for all documentation routes
restOptions.doc.pathstring'/openapi.json'Path to the raw OpenAPI spec (relative to base)
restOptions.ui.pathstring'/explorer'Path to the documentation UI (relative to base)
restOptions.ui.type'swagger' | 'scalar''scalar'UI provider type
explorer.openapistring'3.0.0'OpenAPI specification version
explorer.info.title / .version / .description / .contact-Always sourced from package.jsonOverwritten at runtime - binding values are discarded
explorer.serversArray<{ url, description? }>Auto-detected when emptyServer URLs
uiConfigRecord<string, any>undefinedCustom config passed through to the UI provider

Binding keys

KeyConstantTypeRequiredDefault
@app/api-reference/optionsApiReferenceBindingKeys.API_REFERENCE_OPTIONSIApiReferenceOptionsNoSee Options table

SwaggerBindingKeys.SWAGGER_OPTIONS is removed. It was only ever an alias for the key above - there was never a separate binding under the literal '@app/swagger/options', so nothing needs rebinding.

Default value:

typescript
const DEFAULT_API_REFERENCE_OPTIONS: IApiReferenceOptions = {
  restOptions: {
    base: { path: '/doc' },
    doc: { path: '/openapi.json' },
    ui: { path: '/explorer', type: 'scalar' },
  },
  explorer: {
    openapi: '3.0.0',
    info: {
      title: 'API Documentation',
      version: '1.0.0',
      description: 'API documentation for your service',
    },
  },
};

NOTE

The explorer.info values above are never used at runtime - binding() unconditionally overwrites explorer.info from package.json. They exist only as structural defaults.

API endpoints

MethodPath (default, relative to app base)Description
GET/doc/explorerDocumentation UI (Scalar by default)
GET/doc/openapi.jsonRaw OpenAPI specification

These paths are mounted under your application's own base path - path.base in IApplicationConfigs. With the /api base path from the getting-started guide, that's GET /api/doc/explorer. They also shift with restOptions.base.path, restOptions.ui.path, and restOptions.doc.path.

UIProviderFactory

MethodSignatureDescription
getInstance()static () => UIProviderFactoryReturns the singleton instance
register()(opts: { type: string }) => voidInstantiates and registers a built-in provider; idempotent - warns and returns if the type is already bound
getProvider()(opts: IGetProviderParams) => IUIProviderReturns the registered provider or throws Unknown UI Provider
getRegisteredProviders()() => string[]Lists all registered provider type keys

Extends MemoryStorageHelper<{ [key: string | symbol]: IUIProvider }>, using isBound() / get() / set() / keys() for lightweight, type-safe storage without the full DI container.

Type definitions

typescript
type TDocumentUIType = TConstValue<typeof DocumentUITypes>;

class DocumentUITypes {
  static readonly SWAGGER = 'swagger';
  static readonly SCALAR = 'scalar';
  static readonly SCHEME_SET: Set<string>;
  static isValid(input: string): boolean;
}

TDocumentUIType is derived via TConstValue, which extracts the union of every static readonly string on DocumentUITypes. The type stays in sync with the constants automatically.

Component lifecycle (binding())

  1. Resolve options - reads ApiReferenceBindingKeys.API_REFERENCE_OPTIONS with isOptional: true, then merges base/doc/ui each against DEFAULT_API_REFERENCE_OPTIONS
  2. Overwrite info - reads package.json via application.getAppInfo() and replaces explorer.info
  3. Auto-detect servers - builds one entry from application.getServerAddress() when explorer.servers is empty
  4. Normalize paths - ensures every path segment (base.path, doc.path, ui.path) has a leading /
  5. Register the OpenAPI doc route - rootRouter.doc(docPath, explorer)
  6. Resolve uiType - restOptions.ui.type ?? DocumentUITypes.SWAGGER
  7. Validate and register the UI provider - via UIProviderFactory, unless a provider with that type is already bound
  8. Register the UI route - GET handler at uiPath calling uiProvider.render()
  9. Register JWT and Basic security schemes on the OpenAPI registry

Tech stack

LibraryPurpose
@hono/zod-openapiOpenAPI generation from Zod schemas
@hono/swagger-uiSwagger UI rendering
@scalar/hono-api-referenceScalar UI rendering
zodSchema validation and type generation

Troubleshooting

SymptomCauseFix
Invalid document UI TyperestOptions.ui.type is not 'swagger' or 'scalar' - an explicit empty string is NOT repaired by the ?? fallbackUse 'scalar' or 'swagger' explicitly
Documentation UI shows no routesControllers aren't defining routes with Zod schemas via defineRoute, bindRoute, or @api()Add Zod response schemas to your route configs
Unknown UI ProviderUIProviderFactory.getProvider() was called with a type that was never registered - usually a failed binding()Ensure ApiReferenceComponent is registered in preConfigure(); check logs for warnings during binding
OpenAPI spec missing authentication schemesAuthenticationComponent isn't registered, so auth strategies aren't available when schemes are addedRegister AuthenticationComponent before ApiReferenceComponent in preConfigure()
explorer.info doesn't match my bindingexplorer.info is always overwritten from package.json during binding()Update package.json fields (name, version, description, author) instead

See also

Files: