@chaeco/auto-api-docs
v0.0.3
Published
Automatic API documentation generator for Node.js frameworks. OpenAPI 3.0 (JSON/YAML), self-contained HTML, Postman Collection 2.1, and Markdown from @chaeco/auto-router's route registry.
Maintainers
Readme
@chaeco/auto-api-docs
Automatic API documentation generator for Node.js frameworks. Reads @chaeco/auto-router's route registry and produces OpenAPI 3.0 (JSON/YAML), a self-contained static HTML doc site, a Postman Collection 2.1, and Markdown — from the same route metadata you already write.
Features
- 🚀 Zero-config — point it at your app, get four documentation formats
- 📄 OpenAPI 3.0 (JSON + YAML) — drop into Swagger UI, Stoplight, or any codegen tool
- 🌐 Self-contained HTML doc site — single file, no CDN, dark/light theme, search
- 📮 Postman Collection 2.1 — ready to import, with
{{baseUrl}}/{{token}}variables - 📝 Markdown — tag-grouped, version-control friendly
- 🧬 Rich schema metadata —
requestBody,queryParams,pathParams,responsesviacreateHandlermeta - ✅ zod support — declare schemas with zod, converted to JSON Schema automatically
- 🔒 Auth-aware —
bearerAuthsecurity scheme generated fromrequiresAuth - 🧩 Structural decoupling — no runtime dependency on
@chaeco/auto-router
Installation
npm install @chaeco/auto-api-docsUsing zod schemas in route meta? Install the converter (this package never imports zod):
npm install zod zod-to-json-schemaAI Tool Skills
This package includes AI agent skills for Claude Code and OpenAI Codex. After installation, run this once:
npx @chaeco/auto-api-docs init-skillsThis copies skill files into your project's .claude/skills/auto-api-docs/ and .codex/skills/auto-api-docs/. AI tools will then enforce schema-declaration conventions and best practices when writing routes.
Table of Contents
- Quick Start
- Declaring Route Schemas
- Output Formats
- Configuration
- Integration with auto-router
- API Reference
- Example Project
- License
Quick Start
import { autoRouter } from '@chaeco/auto-router'
import { autoApiDocs } from '@chaeco/auto-api-docs'
const app = new YourFramework()
app.extend(autoRouter({ dir: './controllers', prefix: '/api' }))
app.extend(autoApiDocs({ outDir: './docs', baseUrl: 'http://localhost:3000' }))After app.listen() (or immediately if your framework resolves plugins synchronously), ./docs/ contains:
docs/
├── openapi.json # OpenAPI 3.0 spec (JSON)
├── openapi.yaml # OpenAPI 3.0 spec (YAML)
├── index.html # Self-contained HTML doc site
├── postman-collection.json # Postman Collection 2.1
└── api.md # Markdown documentationOpen docs/index.html in a browser — it has search, tag filters, and a dark/light theme toggle with no server needed.
Pure function (no app)
import { generateApiDocs } from '@chaeco/auto-api-docs'
// From an app-like object, or a bare RouteInfo[]:
const docs = await generateApiDocs(app, { formats: { openapi: true } })
// docs.openapiSpec — raw spec for serving in-app
// docs.openapi.json / docs.openapi.yaml — serialized stringsDeclaring Route Schemas
Schemas are declared where your routes are defined — in the meta argument of createHandler from @chaeco/auto-router:
import { z } from 'zod'
import { createHandler } from '@chaeco/auto-router'
const LoginSchema = z.object({
username: z.string().min(1),
password: z.string().min(1),
})
export default createHandler(
async (ctx) => {
const result = LoginSchema.safeParse(ctx.req?.body ?? {})
if (!result.success) { ctx.res.status = 400; return }
ctx.res.body = { token: 'jwt' }
},
{
summary: 'User login',
description: 'Authenticates a user and returns a JWT token.',
tags: ['Auth'],
requestBody: { schema: LoginSchema, description: 'Credentials' },
responses: {
'200': { description: 'Login succeeded', schema: z.object({ token: z.string() }) },
'400': { description: 'Validation failed' },
'401': { description: 'Invalid credentials' },
},
},
)Meta fields
| Field | Type | Description |
|-------|------|-------------|
| summary | string | Short operation title (falls back to description, then "METHOD path") |
| description | string | Longer operation description |
| tags | string[] | Logical grouping for docs (Auth, Users, Posts, …) |
| operationId | string | Explicit OpenAPI operationId |
| deprecated | boolean | Marks the operation deprecated |
| requestBody | Schema \| zod \| RequestBodySpec | Request body schema |
| queryParams | Record<string, ParamSpec> \| Schema \| zod | Query parameters |
| pathParams | Record<string, ParamSpec> | Path parameter metadata |
| responses | Record<string, ResponseSpec> | Response schemas keyed by status ('200', '404', 'default') |
ParamSpec: { description?, required?, type?, enum?, default?, example?, schema? } — type accepts 'string' | 'number' | 'integer' | 'boolean'.
RequestBodySpec: { schema, required?, description?, contentType? }.
ResponseSpec: { description?, schema?, content? } — content is a media-type → schema map.
JSON Schema vs zod
Both work anywhere a schema is accepted:
// Plain JSON Schema
requestBody: { schema: { type: 'object', required: ['name'], properties: { name: { type: 'string' } } } }
// zod — converted automatically
requestBody: { schema: z.object({ name: z.string() }) }zod schemas are detected by duck-typing and converted with zod-to-json-schema resolved from your node_modules (so it runs against your zod instance). No zod import in this package. If the converter is missing you get a descriptive error, or you can inject your own (see Customizing zod conversion).
Type augmentation: importing
@chaeco/auto-api-docsaugments@chaeco/auto-router'sRouteMetaso the fields above are typed when you writecreateHandler(fn, meta).
Output Formats
OpenAPI 3.0
openapi: '3.0.3',servers[0].urlfrombaseUrl.- Multiple methods grouped under one path item.
components.securitySchemes.bearerAuthadded only when at least one route hasrequiresAuth; protected operations getsecurity: [{ bearerAuth: [] }], public onessecurity: [].operationIdderived (GET /api/users/:userId→getApiUsersUserId) or taken from meta, de-duplicated.- Path params (
:param→{param}) alwaysrequired, default{ type: 'string' }; enriched bypathParams. requestBody,responses,tags,deprecatedmapped from meta; unknown meta keys pass through to the operation object.
Static HTML
- Single self-contained file — inline CSS/JS, system font stack, no CDN, no webfonts, no external requests.
- Dark/light theme toggle (persisted), default follows
prefers-color-scheme. - Search filters by method/path/summary/description/tags; tag chips filter sections.
- Endpoints as expandable rows: parameters table, request-body JSON, responses table.
- Method-colored badges,
tabular-numsstats, dividers between tag sections.
Postman Collection 2.1
- Variables:
{{baseUrl}}(frombaseUrl) and{{token}}. - Folder per tag (default) or per first path segment (
groupBy: 'path'); untagged routes go to aDefaultfolder. - Protected requests get bearer auth (
{{token}}); path params become URL variables; optional query params are disabled. - Requests with a
requestBodyinclude a raw JSON body derived from the schema.
Markdown
# title, description, stats, base URL, table of contents.## tag→### path→#### \METHOD /path``; parameter/query tables, request-body JSON block, responses table.- Untagged routes →
## Default.
Configuration
Plugin options
app.extend(autoApiDocs({
outDir: './docs', // output directory
title: 'My API', // document title
version: '1.0.0', // document version
description: 'My public API', // document description
baseUrl: 'https://api.example.com', // OpenAPI servers + Postman {{baseUrl}}
includeTags: ['Auth', 'Users'], // only these tags
excludeTags: ['Internal'], // drop these tags
includePaths: ['/api/users', 'GET /api/auth/*'],
excludePaths: ['/api/admin/*'],
groupBy: 'tag', // 'tag' | 'path'
logging: true,
onLog: (level, msg) => myLogger[level](msg),
}))Output overrides
app.extend(autoApiDocs({
outDir: './docs',
outputs: {
openapi: { json: 'spec.json', yaml: false }, // custom path, skip YAML
html: 'api.html', // custom HTML path
postman: false, // skip Postman
// markdown omitted → writes api.md
},
}))Filtering
includeTags/excludeTags— keep/drop routes whose tags intersect the list.includePaths/excludePaths— same pattern semantics as auto-router'sforcePublic/forceProtected:- Path only (all methods):
'/api/users','/api/admin/*' - Method + path:
'GET /api/users' - Wildcard
/*matches sub-paths, not the base itself
- Path only (all methods):
Customizing zod conversion
For bundler-constrained environments or zod-4 users, pass your own converter:
import { zodToJsonSchema } from 'zod-to-json-schema'
app.extend(autoApiDocs({
zodToJsonSchema: (schema) => zodToJsonSchema(schema),
}))Integration with auto-router
autoApiDocs reads app.$routes.all — the same registry autoRouter() / staticAutoRouter() populate. The input type is structurally compatible with auto-router's RouteInfo, so no casts or glue code are needed. There is no runtime dependency on @chaeco/auto-router; if you don't use auto-router, pass your own RouteInfo[] to generateApiDocs().
API Reference
autoApiDocs(options)
Returns a plugin (app) => Promise<void> for app.extend(). Reads app.$routes.all, generates all four formats, writes them to outDir. Throws if app.$routes.all is missing (run autoRouter() first).
generateApiDocs(input, options)
Pure generator. input is an app-like { $routes: { all: [...] } } or a bare RouteInfo[]. options.formats is an allowlist ({ openapi: true } → only OpenAPI). Throws auto-api-docs: no routes found when empty. Returns { openapi?, html?, postman?, markdown?, openapiSpec?, postmanCollection?, warnings }.
Exported types
ApiDocsOptions, GenerateApiDocsOptions, AutoApiDocsOptions, ApiDocsOutputOptions, GeneratedDocs, GeneratedArtifacts, ApiRouteMeta, ApiRouteInfo, RouteRegistryLike, AppLike, ApiDocsSource, Schema, ZodSchemaLike, ParamSpec, ResponseSpec, RequestBodySpec, ApiDoc, ApiOperation, ApiParam, ApiResponse, ApiRequestBody, GroupBy, RouteMeta.
Example Project
See example/ for a runnable demo: a mock app registering routes with auto-router, rich meta including zod schemas, and autoApiDocs() writing all four formats to ./docs.
