@dunx/openapi
OpenAPI 3.1 documents for dunx controllers, generated from the schemas the routes already validate, with Swagger UI or Scalar mounted over them
An OpenAPI 3.1 document generated from the routes an app already has, with
Swagger UI or Scalar served from the same Bun.serve as everything else.
The difference from a reflection-based generator is what it reads. A route's
schemas are the objects the request path validates against. Its security
metadata (@Public(), @Roles()) is what its guards enforce. Both are already
on the route, so the document is not a second description that can drift from
the first.
Usage
import { HttpFactory } from '@dunx/http';
import { OpenApiModule } from '@dunx/openapi';
import { SwaggerRenderer } from '@dunx/openapi/swagger';
const app = await HttpFactory.create(
OpenApiModule.forRoot({
title: 'Payments',
version: '1.4.0',
root: AppModule, // the graph to document, and the graph that gets imported
// Omit for the document alone: no `renderer`, no page route.
renderer: new SwaggerRenderer({ docExpansion: 'list' }),
}),
);
app.setGlobalPrefix('api');
await app.listen(3000);
// GET /api/docs the explorer
// GET /api/openapi.json the documentforRootAsync takes a factory that may inject, so an app can read its title off
ConfigService or contribute another library's schema.
What is here
The OpenAPI guide is canonical.
| Piece | What it does |
|---|---|
OpenApiModule | Wraps the root it documents, mounts the JSON and a renderer's page |
DocsRenderer | The page contract. PackageAssets serves a renderer's files |
@ApiDoc | Summary, description, tags and deprecation on a route or a class |
@ApiHidden | A real route kept out of the document. Lives in @dunx/http |
describeRoutes | The routes as data, constructing nothing |
generateDocument | The document from those routes |
betterAuthDocument | better-auth's own paths merged in. Lives in @dunx/auth |
bunx dunx-openapi | The document written to a file, with no container and no server |
Security comes from the same @Public() and @Roles() metadata the guards read
at runtime, so there is no second annotation for the document to disagree with.
A response schema is documentation only: the verb decorators hold the handler's
return type to it at compile time instead of validating every response.
authorize decides who may see the document, the page and the page's assets. It
takes the same Authorize as @dunx/dashboard. A refused request gets a 404. If
authorize returns a Response, for example a redirect to sign in, that response
is sent as is.
Notes
- Add
.meta({ id })to a zod schema to put it incomponents/schemas. Without an id it is inlined wherever it is used..strict()after.meta()drops the metadata, so call.meta()last. - Prose belongs in
description. An explorer labels a schema bytitle, which@dunx/openapifills with the component name. - The page embeds the document rather than fetching it, and loads the renderer's
files same-origin from your own install. Nothing reaches a CDN, unless you turn
Scalar's
withDefaultFontsback on. renderersits besiderootrather than inforRootAsync's factory: the controller declares its routes before there is a container to run one.