Skip to content

API Reference

Automatic interactive API documentation generated from OpenAPI specs, rendered by a pluggable UI provider - Scalar by default, or classic Swagger UI.

Renamed from SwaggerComponent

Swagger UI is just one of the pluggable UI providers, so the component carries a vendor-neutral name. SwaggerComponent, ISwaggerOptions, and SwaggerBindingKeys remain available as deprecated aliases - existing applications keep working unchanged.

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 - the docs UI comes up at /doc/explorer, the raw spec at /doc/openapi.json. No configuration required.

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 unconditionally reads your package.json (via application.getAppInfo()) and 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(), 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. A quirk to know if you inspect the rendered output or write a custom UI provider: Scalar's own API takes pageTitle, not title.
  • Security schemes are always registered. JWT (bearer) and Basic security schemes are added to the OpenAPI registry unconditionally, so routes using authenticate: { strategies: ['jwt'] } or ['basic'] render the correct auth UI.

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 /api-docs/explorer, spec at /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 a deprecated alias whose VALUE is the same '@app/api-reference/options' string - there is no separate binding under the literal '@app/swagger/options'.

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)Description
GET/doc/explorerDocumentation UI (Scalar by default)
GET/doc/openapi.jsonRaw OpenAPI specification

Actual paths 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: