ExisJS

OpenAPI & Swagger Documentation

ExisJS includes a native OpenAPI 3.1 documentation engine. It automatically introspects registered routes, parses tex and Zod validation schemas, and serves interactive documentation UIs alongside raw JSON specifications.


Enabling Documentation

Configure documentation in exis.config.ts using defineConfig:

exis.config.ts
import { defineConfig } from 'exisjs/config'
export default defineConfig({  docs: {    enabled: true,              // Mounts interactive UI and spec endpoint    path: '/docs',              // Base path for UI (default: '/docs')    specPath: '/docs/json',     // OpenAPI 3.1 JSON endpoint (default: '/docs/json')    title: 'Store API Documentation',    version: '1.0.0',    description: 'REST API reference with native schema introspection',    ui: 'swagger-ui',          // 'swagger-ui' | 'scalar'  }})

When enabled:

  • Interactive UI: Navigate to http://localhost:3000/docs in your browser.
  • OpenAPI 3.1 Spec: Access the JSON schema at http://localhost:3000/docs/json (or /docs/openapi.json).

Interactive Documentation UIs

ExisJS supports two documentation renderers:

1. Swagger UI (ui: 'swagger-ui', default)

The standard interactive OpenAPI explorer featuring live request execution (Try it out), parameter testing, and schema validation previews.

2. Scalar API Reference (ui: 'scalar')

A modern, dark-mode-first API reference viewer featuring integrated multi-language code snippets, instant search, and response previews.

exis.config.ts
import { defineConfig } from 'exisjs/config'
export default defineConfig({  docs: {    enabled: true,    ui: 'scalar', // Use Scalar instead of Swagger UI  }})

1. Functional Paradigm (route.*)

In functional file-system routing, specify documentation metadata directly inside route.get(), route.post(), and other method definitions. Schema properties (query, params, headers, body) defined using tex are automatically converted to OpenAPI 3.1 parameter and request body schemas.

src/http/users/route.ts
import { controller, route } from 'exisjs/router'import { tex, getPaginationSkip } from 'exisjs/validator'
export default controller({  list: route.get('/', {    summary: 'List active users',    description: 'Returns a paginated list of all active users in the system.',    tags: ['Users'],    query: tex.pagination({ defaultLimit: 25, maxLimit: 100 }),    responses: {      200: {        description: 'Paginated user collection',        schema: tex.object({          data: tex.array(            tex.object({              id: tex.uuid(),              name: tex.string(),              email: tex.email(),            })          ),          total: tex.number(),          skip: tex.number(),          limit: tex.number(),        }),      },      400: { description: 'Invalid query pagination parameters' },    },    async handle({ query }) {      const { skip, limit } = getPaginationSkip(query)      const [data, total] = await Promise.all([        db.users.findMany({ skip, take: limit }),        db.users.count(),      ])
      return { data, total, skip, limit }    },  }),
  create: route.post('/', {    summary: 'Create a new user',    tags: ['Users'],    body: tex.object({      name: tex.string({ min: 2 }),      email: tex.email(),      role: tex.enum(['admin', 'member', 'guest']).optional(),    }),    responses: {      201: { description: 'User successfully created' },      400: { description: 'Validation failure' },    },    async handle({ body }) {      return await db.users.create(body)    },  }),
  getById: route.get('/:id', {    summary: 'Get user details by ID',    tags: ['Users'],    params: tex.object({      id: tex.uuid(),    }),    responses: {      200: { description: 'User details found' },      404: { description: 'User not found' },    },    async handle({ params }) {      return await db.users.findById(params.id)    },  }),})

2. Class-Based (OOP) Paradigm (@Api* Decorators)

In class-based controllers in route.ts, annotate classes and route methods with documentation decorators from exisjs/swagger:

src/http/products/route.ts
import { Controller, Get, Post, Body, Param, Query } from 'exisjs/decorators'import {  ApiTags,  ApiOperation,  ApiResponse,  ApiBearerAuth,  ApiExclude,} from 'exisjs/swagger'
@ApiTags('Products')@ApiBearerAuth()@Controller()export default class ProductsController {  @Get('/')  @ApiOperation({    summary: 'List products',    description: 'Fetches paginated catalog items',  })  @ApiResponse({ status: 200, description: 'List of catalog products' })  async list(@Query('page') page: number = 1) {    return [      { id: 'prod_1', name: 'Mechanical Keyboard', price: 99 },      { id: 'prod_2', name: 'Ergonomic Mouse', price: 49 },    ]  }
  @Post('/')  @ApiOperation({ summary: 'Create new product' })  @ApiResponse({ status: 201, description: 'Product created successfully' })  @ApiResponse({ status: 400, description: 'Invalid product payload' })  async create(@Body() dto: { name: string; price: number }) {    return { id: 'prod_3', ...dto }  }
  @Get('/:id')  @ApiOperation({ summary: 'Get product by ID' })  @ApiResponse({ status: 200, description: 'Product found' })  @ApiResponse({ status: 404, description: 'Product not found' })  async getById(@Param('id') id: string) {    return { id, name: 'Mechanical Keyboard', price: 99 }  }
  @Get('/internal/metrics')  @ApiExclude() // Excluded from generated Swagger documentation  async getInternalMetrics() {    return { uptime: process.uptime() }  }}

Decorator Reference

DecoratorTargetDescription
@ApiTags(...tags)Class / MethodGroups operations under specified tag sections in the documentation UI.
@ApiOperation(options)MethodSpecifies summary, description, operationId, and deprecated status.
@ApiResponse(options)MethodDocuments response status codes, descriptions, and schemas.
@ApiBearerAuth(name?)Class / MethodApplies Bearer JWT authentication requirement to the endpoint.
@ApiSecurity(name, scopes?)Class / MethodApplies a custom security scheme requirement to the endpoint.
@ApiExclude()Class / MethodExcludes the controller or endpoint from generated documentation.
@ApiProperty(options)Class PropertyAnnotates DTO class properties with description, examples, and enums.

Boundary Cascading Documentation (boundary.ts)

Boundaries (boundary.ts) apply shared tags or security requirements to all nested routes within a directory hierarchy:

src/http/admin/boundary.ts
import { defineBoundary } from 'exisjs/router'
export const config = defineBoundary({  tags: ['Admin Portal'],  security: [{ bearerAuth: [] }],})

Schema Introspection & Multi-Validator Support

The OpenAPI generator inspects route validation schemas and automatically translates them into OpenAPI 3.1 JSON Schemas.

1. Native tex Schemas

All tex primitives, options, and metadata methods translate into OpenAPI properties:

src/http/users/schema.ts
import { tex } from 'exisjs/validator'
export const UserRegistrationSchema = tex.object({  username: tex.string({ min: 3, max: 30 })    .describe('Unique alphanumeric handle')    .example('alex_dev'),  email: tex.email()    .describe('Primary contact email address')    .example('[email protected]'),  age: tex.number({ min: 18 })    .describe('User age in years')    .example(25),  role: tex.enum(['admin', 'member', 'guest'] as const)    .describe('Assigned account authorization level')    .example('member'),})

Discriminated Unions in OpenAPI

When using tex.discriminatedUnion(), the generator generates an OpenAPI oneOf schema along with the discriminator mapping:

src/http/events/schema.ts
export const PaymentEvent = tex.discriminatedUnion('method', [  tex.object({ method: tex.literal('card'), cardNumber: tex.creditCard() }),  tex.object({ method: tex.literal('crypto'), walletAddress: tex.string() }),])

Translates to OpenAPI 3.1:

{  "oneOf": [    {      "type": "object",      "properties": { "method": { "type": "string", "enum": ["card"] }, "cardNumber": { "type": "string" } },      "required": ["method", "cardNumber"]    },    {      "type": "object",      "properties": { "method": { "type": "string", "enum": ["crypto"] }, "walletAddress": { "type": "string" } },      "required": ["method", "walletAddress"]    }  ],  "discriminator": {    "propertyName": "method"  }}

2. Third-Party Validators (Zod, ArkType, JSON Schema)

ExisJS uses multi-validator duck typing to introspect external schemas without requiring adapter packages:

src/http/orders/route.ts
import { controller, route } from 'exisjs/router'import { z } from 'zod'
const OrderSchema = z.object({  item: z.string().min(2).describe('Product title'),  quantity: z.number().int().positive().describe('Item count'),  coupon: z.string().optional(),})
export default controller({  create: route.post('/', {    summary: 'Place an order',    body: OrderSchema,    async handle({ body }) {      return { success: true, order: body }    },  }),})

Security Schemes Configuration

Define authentication mechanisms (e.g. Bearer JWT, API Keys, OAuth2) in exis.config.ts:

exis.config.ts
import { defineConfig } from 'exisjs/config'
export default defineConfig({  docs: {    enabled: true,    securitySchemes: {      bearerAuth: {        type: 'http',        scheme: 'bearer',        bearerFormat: 'JWT',        description: 'Enter your JWT token: Bearer <token>',      },      apiKeyAuth: {        type: 'apiKey',        in: 'header',        name: 'X-API-KEY',        description: 'Static API Key passed in headers',      },    },    // Global default security (can be overridden per route)    security: [      { bearerAuth: [] },    ],  },})

Configuration Reference (SwaggerConfig)

The full set of options supported under docs in exis.config.ts:

OptionTypeDefaultDescription
enabledbooleanfalseEnables documentation mounting and OpenAPI spec generation.
pathstring'/docs'URL path where the interactive documentation UI is served.
specPathstring'/docs/json'URL path where the raw OpenAPI 3.1 JSON specification is served.
titlestring'ExisJS API Documentation'Title displayed in the documentation header.
versionstring'1.0.0'API Version displayed in documentation.
descriptionstring—API description markdown shown below the header.
termsOfServicestring—URL to terms of service agreement.
contactobject—Contact information (name, url, email).
licenseobject—License information (name, url).
serversOpenApiServer[]—Target environments (e.g., Production, Staging).
tagsOpenApiTag[]—Global list of tag metadata and external documentation links.
ui'swagger-ui' | 'scalar''swagger-ui'Interactive documentation UI renderer.
customCssstring—Custom CSS styles injected into the Swagger/Scalar page.
customFaviconstring—Custom favicon URL for the documentation page.
transformSpec(spec) => spec—Synchronous hook to programmatically mutate the generated OpenAPI spec.

Programmatic Mount Helper (swagger())

You can also mount Swagger documentation programmatically inside custom server bootstrapping in server.ts:

src/http/server.ts
import { exis } from 'exisjs'import { swagger } from 'exisjs/swagger'
export default exis({  async onStart(app) {    swagger({      title: 'Custom API Gateway',      version: '2.0.0',      path: '/api/docs',    })(app)  },})