Skip to Content
📖 Guide DocumentsSwagger DocumentSwagger Builder

Swagger / OpenAPI

Nestia derives the entire OpenAPI document from your TypeScript types. No @ApiProperty, no @ApiResponse, no separate schema file. Two ways to produce the document:

  • npx nestia swagger — emit a swagger.json file at build time.
  • NestiaSwaggerComposer.document(app) — compose the document inside a running app, serve it through SwaggerModule.setup().

Both read the same metadata the Nestia transformer attached at compile time.


Runtime composition

The simplest path. Build the document at app start; serve it through Swagger UI:

src/main.ts
import { NestiaSwaggerComposer } from "@nestia/sdk"; import { INestApplication } from "@nestjs/common"; import { NestFactory } from "@nestjs/core"; import { SwaggerModule } from "@nestjs/swagger"; import { AppModule } from "./AppModule"; const main = async () => { const app: INestApplication = await NestFactory.create(AppModule); const document = await NestiaSwaggerComposer.document(app, { openapi: "3.1", servers: [{ url: "http://localhost:3000", description: "Local" }], }); SwaggerModule.setup("docs", app, document as any); await app.listen(3000); }; main().catch(console.error);

Open http://localhost:3000/docs and the Swagger UI loads with your full API. The raw JSON is at http://localhost:3000/docs-json.

Build requirement

NestiaSwaggerComposer needs the @nestia/sdk transform active at build time. Build with ttsc; it discovers the Nestia and typia transforms from package manifests. You do not need a compilerOptions.plugins array.

If the app was built with tsc, SWC, Babel, or nest build, NestiaSwaggerComposer can find zero routes and emit an empty document. The fix is to rebuild through ttsc.


File generation

For CI pipelines, static hosting, or downstream codegen, emit a file instead:

nestia.config.ts
import { INestiaConfig } from "@nestia/sdk"; import { NestFactory } from "@nestjs/core"; import { AppModule } from "./src/AppModule"; const config: INestiaConfig = { input: () => NestFactory.create(AppModule), output: "src/api", swagger: { openapi: "3.1", // or "3.0" / "2.0" if your tooling needs it output: "dist/swagger.json", info: { title: "My App", version: "1.0.0", }, servers: [ { url: "http://localhost:3000", description: "Local" }, { url: "https://api.example.com", description: "Production" }, ], security: { bearer: { type: "apiKey", name: "Authorization", in: "header" }, }, beautify: true, }, }; export default config;

Run:

Terminal
npx nestia swagger # or, all three artifacts at once: npx nestia all

Output: dist/swagger.json. Feed it to Swagger UI, Redoc, Postman imports, the Nestia Editor, or @nestia/chat.

For local live reload workflows, run the swagger generator in watch mode:

Terminal
npx nestia swagger --watch

Watch mode writes the initial document, then regenerates it when controller, nestia.config.ts, or tsconfig.json inputs change. Point your dev server or Swagger UI reload tooling at the generated file.

OpenAPI versions

The openapi field accepts "3.2" (default), "3.1", "3.0", or "2.0". Nestia composes the document at the newest version and downgrades on the way out — your type fidelity is highest at the newest version, but the document is still legal at older ones.

A Swagger 2.0 document holds less than an OpenAPI 3.x one, so with openapi: "2.0" the generator leaves out or narrows what 2.0 has no place for instead of failing: named examples and request body examples, the x-nestia-encrypted flag (the description keeps the encryption warning), a form body’s own description, and the bodies of exceptions whose media type differs from the success body’s. A form field taking several files, or a file or null, is listed as one file, optional unless the field requires at least one.


What ends up in the schema

The transformer walks each @Typed* decorator and produces:

  • Path / query / body / header schemas from the parameter types.
  • Response schemas from the return type. 200 / 201 are inferred from the HTTP method. A route whose return type carries no value — void, undefined, never — still declares its status, with no response body attached; the same holds for a @TypedException<void>().
  • Error responses from @TypedException() decorators.
  • summary / description from the controller method’s JSDoc.
  • Field descriptions, examples, titles from JSDoc on DTO properties.
  • Readonly array hints through x-readonly-array for TypeScript readonly T[] and ReadonlyArray<T> positions. Property-level readonly prop still maps to OpenAPI readOnly.
  • Constraints (minLength, format, pattern, …) from tags.* intersections.

Routes using vanilla @Get / @Body appear too, described from their declared types; they only lose runtime validation. To leave a route out of the document, tag the method @internal or @hidden, or @ignore to leave it out of every generator.


Adding documentation

Use JSDoc on the controller method and DTO fields:

import { tags } from "typia"; /** * Article payload. */ export interface IArticleCreate { /** * Article title. * * @example "Hello Nestia" */ title: string & tags.MinLength<3> & tags.MaxLength<50>; /** * Markdown body. */ body: string; } @Controller("articles") export class ArticlesController { /** * Create article * * Stores a new article and returns its assigned ID. * * @tag articles */ @TypedRoute.Post() public async create(@TypedBody() input: IArticleCreate): Promise<IArticle> { ... } }

The first JSDoc line becomes the route’s summary, the body becomes the description. Properties get matching schema fields. See Swagger → Strategy for the full list of JSDoc tags Nestia recognizes.


Configuration reference

The swagger block of INestiaConfig:

FieldTypeNotes
outputstringFile path. If a directory, writes swagger.json inside.
openapi"3.2" | "3.1" | "3.0" | "2.0"Defaults to "3.2".
infoPartial<OpenApi.IDocument.IInfo>title, version, description. Falls back to package.json.
serversOpenApi.IServer[]One entry per environment.
securityRecord<string, OpenApi.ISecurityScheme>Bearer, OAuth, API key, etc.
tagsOpenApi.IDocument.ITag[]Tag descriptions (also overridable per-route via JSDoc @tag).
decomposebooleanDefaults to true: one parameter per property of a query DTO. false keeps it one style: form parameter, except in Swagger 2.0. Headers DTOs are always decomposed.
operationId(props) => stringCustom operationId generator.
beautifybooleanPretty-print the JSON output.
additionalbooleanAdds x-nestia-method, x-nestia-namespace (the SDK accessor), and x-nestia-jsDocTags to each operation.

Decomposed query and header properties marked with ? produce parameters with required: false, including when exactOptionalPropertyTypes is enabled.


See also

Last updated on