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
| Item | Value |
|---|---|
| Package | @venizia/ignis |
| Class | ApiReferenceComponent |
| UI Factory | UIProviderFactory |
| Runtimes | Both |
| Provider | Value | When to use |
|---|---|---|
| Scalar | 'scalar' | Modern, clean UI (default) |
| Swagger UI | 'swagger' | Classic Swagger interface |
Import Paths
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.
// 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:
// 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 boundIApiReferenceOptions, then shallow-mergesbase,doc, anduieach against their own defaults - overridingui.typealone still keepsui.pathand everybase/docfield. explorer.infois always overwritten. The component unconditionally reads yourpackage.json(viaapplication.getAppInfo()) and replacesexplorer.infowith{ title, version, description, contact }- anyexplorer.infoyou bind is discarded. Editpackage.jsoninstead.explorer.serversfills in only when empty. A supplied server entry is kept as-is; otherwise the component builds one fromapplication.getServerAddress()plus the base path.- UI type resolution uses
??, not||. The source isrestOptions.ui.type ?? DocumentUITypes.SWAGGER- onlynull/undefinedfalls back, and it falls back to'swagger', not the configured default'scalar'. An explicit empty string is NOT repaired by this fallback - it failsDocumentUITypes.isValid()and the component throwsInvalid document UI Typeimmediately. - UI libraries load lazily.
SwaggerUIProvider/ScalarUIProvidereachawait import()their rendering library insiderender(), on the first request to the docs UI - not at application startup. Only the configured provider's library is ever loaded. ScalarUIProviderrenamestitletopageTitle. A quirk to know if you inspect the rendered output or write a custom UI provider: Scalar's own API takespageTitle, nottitle.- Security schemes are always registered. JWT (
bearer) and Basic security schemes are added to the OpenAPI registry unconditionally, so routes usingauthenticate: { strategies: ['jwt'] }or['basic']render the correct auth UI.
Common tasks
Switch to Swagger UI
this.bind<IApiReferenceOptions>({
key: ApiReferenceBindingKeys.API_REFERENCE_OPTIONS,
}).toValue({ restOptions: { ui: { type: 'swagger' } } });Move the docs under a different base path
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:
UIProviderFactory.getInstance().set('my-ui', new MyCustomUIProvider());Reference
Options
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>;
}| Option | Type | Default | Description |
|---|---|---|---|
restOptions.base.path | string | '/doc' | Base path for all documentation routes |
restOptions.doc.path | string | '/openapi.json' | Path to the raw OpenAPI spec (relative to base) |
restOptions.ui.path | string | '/explorer' | Path to the documentation UI (relative to base) |
restOptions.ui.type | 'swagger' | 'scalar' | 'scalar' | UI provider type |
explorer.openapi | string | '3.0.0' | OpenAPI specification version |
explorer.info.title / .version / .description / .contact | - | Always sourced from package.json | Overwritten at runtime - binding values are discarded |
explorer.servers | Array<{ url, description? }> | Auto-detected when empty | Server URLs |
uiConfig | Record<string, any> | undefined | Custom config passed through to the UI provider |
Binding keys
| Key | Constant | Type | Required | Default |
|---|---|---|---|---|
@app/api-reference/options | ApiReferenceBindingKeys.API_REFERENCE_OPTIONS | IApiReferenceOptions | No | See 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:
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
| Method | Path (default) | Description |
|---|---|---|
GET | /doc/explorer | Documentation UI (Scalar by default) |
GET | /doc/openapi.json | Raw OpenAPI specification |
Actual paths shift with restOptions.base.path, restOptions.ui.path, and restOptions.doc.path.
UIProviderFactory
| Method | Signature | Description |
|---|---|---|
getInstance() | static () => UIProviderFactory | Returns the singleton instance |
register() | (opts: { type: string }) => void | Instantiates and registers a built-in provider; idempotent - warns and returns if the type is already bound |
getProvider() | (opts: IGetProviderParams) => IUIProvider | Returns 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
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())
- Resolve options - reads
ApiReferenceBindingKeys.API_REFERENCE_OPTIONSwithisOptional: true, then mergesbase/doc/uieach againstDEFAULT_API_REFERENCE_OPTIONS - Overwrite info - reads
package.jsonviaapplication.getAppInfo()and replacesexplorer.info - Auto-detect servers - builds one entry from
application.getServerAddress()whenexplorer.serversis empty - Normalize paths - ensures every path segment (
base.path,doc.path,ui.path) has a leading/ - Register the OpenAPI doc route -
rootRouter.doc(docPath, explorer) - Resolve
uiType-restOptions.ui.type ?? DocumentUITypes.SWAGGER - Validate and register the UI provider - via
UIProviderFactory, unless a provider with that type is already bound - Register the UI route -
GEThandler atuiPathcallinguiProvider.render() - Register JWT and Basic security schemes on the OpenAPI registry
Tech stack
| Library | Purpose |
|---|---|
@hono/zod-openapi | OpenAPI generation from Zod schemas |
@hono/swagger-ui | Swagger UI rendering |
@scalar/hono-api-reference | Scalar UI rendering |
zod | Schema validation and type generation |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Invalid document UI Type | restOptions.ui.type is not 'swagger' or 'scalar' - an explicit empty string is NOT repaired by the ?? fallback | Use 'scalar' or 'swagger' explicitly |
| Documentation UI shows no routes | Controllers aren't defining routes with Zod schemas via defineRoute, bindRoute, or @api() | Add Zod response schemas to your route configs |
Unknown UI Provider | UIProviderFactory.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 schemes | AuthenticationComponent isn't registered, so auth strategies aren't available when schemes are added | Register AuthenticationComponent before ApiReferenceComponent in preConfigure() |
explorer.info doesn't match my binding | explorer.info is always overwritten from package.json during binding() | Update package.json fields (name, version, description, author) instead |
See also
Guides:
- Components Overview - Component system basics
- Controllers - Defining OpenAPI routes
Components:
- All Components - Built-in components list
- Authentication - JWT/Basic auth for secured endpoints
Utilities:
- Schema Utilities - Response schema helpers
- JSX Utilities - HTML response schemas
External Resources:
- OpenAPI Specification - OpenAPI standard
- Scalar Documentation - Scalar API documentation UI
- @hono/zod-openapi - Hono OpenAPI integration
Files:
packages/core/src/components/api-reference/component.ts-ApiReferenceComponentpackages/core/src/components/api-reference/ui-factory.ts-UIProviderFactory,SwaggerUIProvider,ScalarUIProviderpackages/core/src/components/api-reference/common/types.ts-IApiReferenceOptions,IUIProvider,IUIConfigpackages/core/src/components/api-reference/common/keys.ts-ApiReferenceBindingKeyspackages/core/src/components/api-reference/common/constants.ts-DocumentUITypes