@json-express/docs-swagger
v2.0.0
Published
Interactive Swagger UI and dynamic OpenAPI 3.0 specification for JSON Express
Downloads
78
Maintainers
Readme
@json-express/docs-swagger
Documentation provider plugin for JSONExpress v2. Renders interactive Swagger UI at
/docsand serves a generated OpenAPI 3.0 specification at/docs/json. The spec is built from your model schemas — not by parsing URLs — so resource grouping and component shapes are authoritative.
What It Does
This plugin implements the IDocProvider interface. When the JSONExpress Kernel boots, it:
- Receives the project's
ModelSchema[]viasetSchemas(schemas)— the same set handed to the database adapter and API generator. - Receives the kernel's
RouteDefinition[]viagetManifest(routes, req)andrenderDocumentation(routes, path, req). - Combines the two: schemas drive what is documented (resource names, field types, required fields, defaults), routes drive how it's exposed (HTTP method, path, security, custom endpoints).
The Transport layer (e.g., @json-express/transport-express) mounts the two HTTP endpoints described below.
Mounted Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /docs | Interactive Swagger UI — full HTML shell, loads [email protected] from CDN. |
| GET | /docs/json | Raw OpenAPI 3.0 spec — the same JSON the UI consumes, useful for codegen and CI checks. |
The mount path is configurable via jex.docs.path (default /docs).
Schema-Driven OpenAPI
For every ModelSchema registered with the kernel that declares fields:
components.schemas.<Collection>is generated fromschema.fields. Field-level options become OpenAPI keywords:types.string({ minLength, maxLength })→{ type: 'string', minLength, maxLength }types.number({ min, max })→{ type: 'number', minimum, maximum }types.date()→{ type: 'string', format: 'date-time' }types.relation({ target })→{ type: 'string', description: 'FK → <target>' }{ required: true }→ added to the operation'srequiredarray{ default }→ preserved asdefault
- Operations are tagged with the resource name (capitalized collection name). Path-segment matching uses the schema's
nameas the source of truth — longest-match wins, soalbums-archiveis never shadowed byalbums. - Request bodies for
POST/PATCH/PUT$refthe component schema (#/components/schemas/<Collection>) by default. - Response bodies
$refthe component schema for resource routes; fall back to{ type: 'object' }when no schema match is found (e.g., bespoke routes underroutes/).
If a project has no models/ folder, schemas are inferred from data/*.json and the same generation pipeline applies — every demo gets useful Swagger output for free.
Fieldless models (defineRoutes(...) or defineModel({ ... }) without a fields block) are skipped from components.schemas — there's no entity to describe. Their custom endpoints are still documented via the per-route loop below, with a generic { type: 'object' } request body unless a more specific one is wired in.
Inter-Package Integration
Three sanctioned channels (see context/INTER_PACKAGE_ARCHITECTURE.md) let other plugins enrich the Swagger output without coupling:
1. route.metadata.validation — per-op validators
@json-express/api-rest stamps each generated route with the model's relevant validation block, in the shape the validation middleware understands:
// CRUD routes — keyed by op
route.metadata.validation = { create?: { body }, update?: { body }, list?: { query } };
// Custom endpoints — flat
route.metadata.validation = { body?, query? };When present, this plugin converts the validator to OpenAPI and uses it in place of the model-derived component schema for that single operation — useful when validation rules are stricter or differently shaped than persisted fields (e.g. a confirmPassword field that's never stored).
Status: the introspection codepath in
docs-swaggerstill reads the legacyroute.metadata.schemakey. Until that's migrated to the newroute.metadata.validationshape above, validators authored in models won't show up as OpenAPI request-body overrides. The component schema (fromfields) is still emitted correctly.
2. route.metadata.isProtected — Bearer auth
When @json-express/middleware-auth flags a route as protected, the operation gets security: [{ bearerAuth: [] }] and the spec's components.securitySchemes.bearerAuth is populated with { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }. Swagger UI's Authorize button then unlocks "Try it out" calls with a token.
3. kernel.context — global state
Not used by this plugin today — but the door is open for, say, a future plugin-versioning to pin the spec's info.version at boot.
Configuration
All options use the jex or JEX namespace:
# Activate this plugin instead of the default docs-light
jex.docs=@json-express/docs-swagger
# Override the mount path (default: /docs)
jex.docs.path=/api-docs
# Override the displayed servers[].url — useful behind a reverse proxy
jex.docs.baseUrl=https://api.example.com/v1
# When api-rest mounts under a prefix, the spec auto-prepends it
jex.api.rest.prefix=/api/v1baseUrl resolution falls through this chain, highest precedence first:
jex.docs.baseUrl(hardcoded override)x-forwarded-proto+x-forwarded-host(reverse-proxy aware)req.protocol+req.headers.host+jex.api.rest.prefix
Installation
npm install @json-express/docs-swaggerThen activate it:
jex.docs=@json-express/docs-swaggerThe CLI's auto-discovery will swap out the default docs-light provider on the next boot.
Architecture Note
Kernel.boot()
├─ schemas finalized (user models + plugin-contributed)
│ │
│ ▼
│ docProvider.setSchemas(schemas) ← source of truth handed in once
│
├─ apiGenerator.generate(collections) → routes[]
│
└─ on first request to /docs:
docProvider.renderDocumentation(routes, '/docs', req) → HTML shell
docProvider.getManifest(routes, req) → OpenAPI 3.0 spec
│
├─ generate components.schemas from ModelSchema[] (skipping fieldless models)
├─ for each route: tag by matching path against schema names
├─ honor route.metadata.validation as request-body override (see status note above)
└─ honor route.metadata.isProtected for security schemeThe plugin holds no shared state between requests — setSchemas is the only stateful call, and it happens once at boot. Spec generation per request is pure given (routes, req) plus the captured schema set.
