Skip to main content

OpenAPI

The framework generates an OpenAPI 3.1 document from your controllers. Paths, parameters, request bodies, security, and tags are all derived from the route definitions you already write — point Swagger UI, Redoc, or a client-SDK generator at the output.

Generate

node src/cli.ts openapi # print to stdout
node src/cli.ts openapi --output openapi.json # write to a file

Add a script to package.json:

"openapi": "node src/cli.ts openapi"
npm run openapi -- --output openapi.json
note

The command loads your controllers but opens no database or network connection and binds no port — it walks the route registry and reads your schemas in-process. It's safe to run in CI. (Unlike codegen, which is on your hot path, this is a cold, occasional command, so it loads the real controller instances to read live schema objects.)

What gets documented

Everything comes from the route definitions you already have — there is nothing OpenAPI-specific to maintain separately:

OpenAPI fieldSource
operationIdhandler method name
tagscontroller class name
summaryroute description
path parameters:name path segments
query parametersroute query: schema (+ middleware query schemas)
requestBodyroute request: schema or content-type map (+ middleware request schemas)
securitymiddleware static get usedAuthParameters()
info / serversyour package.json + the http config (port, myDomain)

Output is OpenAPI 3.1 (JSON Schema 2020-12) only.

Request bodies come from your schemas

Body and query shapes are produced by introspecting the same validation schemas you already use at runtime — through the validator driver's toJsonSchema. How much detail you get depends on the validator:

ValidatorOpenAPI body
Zodfull JSON Schema via native z.toJSONSchema — types, formats, min/max, patterns, enums, defaults
YupJSON Schema from .describe() — types, required, enums, nullable, arrays, datedate-time
ArkType / any schema exposing a .toJsonSchema() methodits native output
defineSchema / a hand-rolled ~standard functionnot introspectable — a placeholder schema + a warning

:::note Use a declarative schema if you want a documented body defineSchema and custom ~standard functions are imperative — they validate but expose no shape, so the generator can't describe them and emits a placeholder object instead. If an endpoint's body should appear in the spec, declare it with Zod or Yup. The command prints a warning listing every schema it couldn't introspect, e.g.:

OpenAPI: 1 schema(s) could not be fully introspected:
POST /auth/login body: schema introspection unavailable — placeholder emitted.

:::

This is why the generator must load your controllers at runtime rather than read the generated types: JSON Schema can only be produced from the live schema object (z.toJSONSchema(...), schema.describe()), not from a TypeScript type.

Documenting auth (security schemes)

A middleware advertises the security scheme(s) it enforces with a static get usedAuthParameters() getter. The generator reads it off the class — no instantiation — adds each entry to components.securitySchemes, and attaches a security requirement to every operation whose middleware chain includes that middleware.

import AbstractMiddleware from "@adaptivestone/framework/services/http/middleware/AbstractMiddleware.js";

class TokenAuth extends AbstractMiddleware {
static get usedAuthParameters() {
return [
// http bearer scheme
{ name: "bearerAuth", type: "http", scheme: "bearer", description: "Bearer token" },
// or an apiKey header
{ name: "X-Api-Key", type: "apiKey", in: "header", description: "API key" },
];
}

async middleware(req, res, next) {
// ... runtime auth logic ...
}
}
FieldMeaning
namescheme key in components.securitySchemes (for apiKey, also the header/query name)
type'apiKey' or 'http'
infor apiKey: 'header' (default) / 'query' / 'cookie'
schemefor http: 'bearer', 'basic', …
descriptionshown in the docs UI

The built-in GetUserByToken already declares its Authorization header + bearer schemes, so any route behind it is documented as secured automatically.

Current limitations

  • Response bodies are not yet schema-documented. Every operation carries a generic 200/400/401/404 response with a text description but no body schema. (Documenting response shapes is on the roadmap — it needs a declared response: schema, since a response has no runtime schema object to introspect.)
  • Catch-all ({*splat}) routes are approximated as a single {splat} path parameter, because OpenAPI has no catch-all; the command warns when it does this.
  • The route description becomes the operation summary; there is no separate long description, deprecated flag, or per-tag description yet.