Skip to content

BeTomorrow/openapi-static-gen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

openapi-gen

Build-time OpenAPI 3.0 / 3.1 generator that parses NestJS controllers and TypeScript DTOs with oxc-parser.

Usage

Project-specific settings (parameter descriptions, tag mapping, API versioning) live in an openapi-gen.config.json at the consuming project root.

Generate from every *.controller.ts under src/:

node --import tsx packages/openapi-gen/src/cli.ts \
  --root packages/back \
  --all-controllers \
  --out specs/generated/openapi.json \
  --config openapi-gen.config.json

Generate from a single controller:

node --import tsx packages/openapi-gen/src/cli.ts \
  --root packages/back \
  --controller src/editorial/controller/page/home-page.controller.ts \
  --out specs/generated/home-page.openapi.json \
  --config openapi-gen.config.json \
  --title "Home page API"

Lint the generated spec with Spectral (duplicate operationId, missing tags, path parameter mismatches, etc.):

node --import tsx packages/openapi-gen/src/cli.ts \
  --root packages/back \
  --all-controllers \
  --out specs/generated/openapi.json \
  --config openapi-gen.config.json \
  --lint

Use a custom ruleset (e.g. the consuming project’s .spectral.js):

  --lint --lint-ruleset .spectral.js

Spectral errors fail the command (exit code 1); warnings are reported but do not fail.

Configuration

Optional openapi-gen.config.json in the project root (or pass --config <path>):

{
  "title": "My API",
  "parameterDescriptions": {
    "id": "Resource identifier"
  },
  "tagsBySourceFolder": {
    "authentication": "auth"
  },
  "preferredApiVersion": "3.0",
  "openApiVersion": "3.1",
  "versionHeader": "X-Api-Version",
  "lintOnGenerate": true,
  "spectralRulesetPath": ".spectral.js"
}
  • title — default OpenAPI info title (overridable via --title)
  • parameterDescriptions — descriptions keyed by parameter name
  • tagsBySourceFolder — maps src/<folder>/... to OpenAPI tags
  • preferredApiVersion — when duplicate path/method exist, prefer this Nest route version
  • openApiVersion — OpenAPI spec to emit: 3.0 (default) or 3.1
  • versionHeader — header name added for @Version(...) routes
  • lintOnGenerate — run Spectral after writing the spec (same as --lint)
  • spectralRulesetPath — custom Spectral ruleset; default is a built-in ruleset tuned for generated specs

How it works

  1. Parse controller ASTs and extract routes, params, and return types.
  2. Resolve DTO imports against the project root.
  3. Build components.schemas from TypeScript declarations.
  4. Attach JSDoc/TSDoc block comments to operation and schema descriptions.
  5. Emit a standalone OpenAPI JSON document (3.0.0 by default, or 3.1.0 when configured).

OpenAPI 3.1

Set "openApiVersion": "3.1" to emit openapi: 3.1.0 with jsonSchemaDialect and JSON Schema–style nullability:

  • nullable: true on a string → type: ["string", "null"]
  • nullable $refanyOf: [{ $ref: ... }, { type: "null" }]
  • Promise<null> responses → type: "null"

Internal generation still uses OpenAPI 3.0 nullable until the final serialization pass, so 3.0 remains the default for existing tooling.

JSDoc/TSDoc

Leading /** ... */ block comments are matched to the next declaration by source position:

  • Controller methods → summary (first sentence) and description (full text when longer)
  • Classes, interfaces, and type aliases → schema description
  • Properties and fields → property description

Controller method tags are also translated:

  • @param name ...parameters[].description (matched by TS variable name or HTTP name) or requestBody.description for @Body params
  • @returns / @returnresponses['200'].description
  • @throws NotFoundException ...responses['404'].description (NestJS HttpException names are mapped to status codes)

Config parameterDescriptions are used as a fallback when @param is absent.

Error and success responses

Beyond @throws, the generator infers responses from controller code:

  • throw new NotFoundException(...) and similar NestJS HTTP exceptions
  • getOrUnauthorized() / getOrBadRequest() helper calls
  • @UseGuards(BearerGuard)401, throttling guards → 429
  • @HttpCode(HttpStatus.CREATED) → success response uses 201 instead of 200

@throws JSDoc descriptions override inferred defaults when both are present. Optional config keys guardStatusCodes and exceptionDescriptions can customize mappings.

Security schemes

@UseGuards(...) decorators are mapped to OpenAPI security:

  • BearerGuardsecurity: [{ bearerAuth: [] }]
  • AnonymousOrBearerGuard → optional bearer auth: security: [{ bearerAuth: [] }, {}]
  • Throttling / rate-limit guards affect 401/429 responses only (not security)

Defaults are emitted under components.securitySchemes.bearerAuth (HTTP bearer JWT). Customize via config:

  • securitySchemes — declare or override scheme definitions
  • guardSecuritySchemes — map guard class names to scheme names (or { scheme, optional })
  • skipSecurityDecorators — decorator names (e.g. Public) that suppress security on a route

Enums and as const literals

  • export enum OrderType { ... }components.schemas.OrderType with enum values
  • export const gender = ['F', 'M'] as const + type Gender = (typeof gender)[number] → string enum schema
  • Class fields with type = 'film-list' as const continue to produce discriminated oneOf variants

class-validator constraints

DTO class properties are enriched from class-validator (and class-transformer) decorators:

  • @IsOptional() / @IsNotEmpty()required on the parent schema
  • @IsString(), @IsNumber(), @IsBoolean(), @IsArray(), @IsObject(), @IsInt()
  • @IsEmail(), @IsDateString(), @IsUUID(), @IsUrl()format
  • @Min(), @Max(), @MinLength(), @MaxLength(), @Length(), @ArrayMinSize(), @ArrayMaxSize()
  • @IsEnum(MyEnum) / @IsIn(constArray)enum (const arrays resolved via imports)
  • @ValidateNested() + @Type(() => ChildDto) — nested $ref / array items
  • @Exclude() — property omitted from the schema

Utility types

Common TypeScript utility types are resolved structurally:

  • Partial<T> — same properties, no required array
  • Required<T> — all properties marked required
  • Pick<T, 'a' | 'b'> / Omit<T, 'a' | 'b'> — subset of object properties
  • Record<K, V>additionalProperties schema (unknown → open object)
  • Array<T> / ReadonlyArray<T>type: array with items
  • Readonly<T> — same as T in OpenAPI output

Generic wrapper types

Single type-parameter classes such as CollectionDto<T> and TaxonomyDto<T> are instantiated at use sites:

  • CollectionDto<CinemaGroupDto>components.schemas.CollectionDto_CinemaGroupDto with data: CinemaGroupDto[]
  • class Foo extends TaxonomyDto<string[]>$ref to TaxonomyDto_Array_string
  • Union type args: CollectionDto<A | B>items.oneOf for array elements

Bare generic templates (e.g. CollectionDto without type args) are not registered as empty components.

Limitations (v1)

  • Structural AST parsing only — no TypeScript type checker.
  • Request bodies are inferred from @Body() parameter types only (application/json).
  • Indexed access (Foo['bar']), conditional types, and multi-parameter generics (Map<K,V> beyond utility types) are not fully resolved.
  • Class and interface extends emit allOf: [{ $ref: Parent }, { properties: ... }] (child-only properties in the second fragment).
  • External package types fall back to open objects.
  • Error responses are not inferred from thrown exceptions.

About

Generate OpenAPI specs directly from typescript source files

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors