ExisJS

Swagger & OpenAPI 3.1 Documentation

ExisJS comes with a native, built-in Swagger & OpenAPI 3.1 documentation engine. It automatically discovers all registered routes, introspects Tex and Zod validation schemas, and serves interactive API documentation at /docs.


Quick Start

Enable Swagger documentation in your exis.config.ts:

exis.config.ts
import { defineConfig } from 'exisjs/config'
export default defineConfig({  docs: {    enabled: true,              // Mounts UI at /docs and spec at /docs/json    title: 'Store API Documentation',    version: '1.0.0',    description: 'Auto-generated API documentation with native schema validation',    ui: 'swagger-ui',          // 'swagger-ui' | 'scalar'  }})

Once enabled:

  • Visit http://localhost:3000/docs to view the interactive documentation UI.
  • Visit http://localhost:3000/docs/json (or /docs/openapi.json) for the raw OpenAPI 3.1 specification.

Interactive Documentation UIs

ExisJS supports two built-in UI renderers:

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

The industry-standard interactive Swagger explorer featuring Try it out, live request execution, and parameter inspection.

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

A modern, high-performance, dark-mode-first API reference documentation viewer with integrated code snippets and fast search.

exis.config.ts
import { defineConfig } from 'exisjs/config'
export default defineConfig({  docs: {    enabled: true,    ui: 'scalar', // Switches UI to modern Scalar  }})

Documenting Functional Routes

In ExisJS folder-based routing, each endpoint folder contains a route.ts file that exports a default controller({ ... }). You can add OpenAPI metadata directly to each route definition:

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 list',        schema: tex.object({          data: tex.array(tex.object({ id: tex.uuid(), name: tex.string() })),          total: tex.number(),        }),      },      400: { description: 'Invalid query parameters' },    },    async handle({ query }) {      const { skip, limit } = getPaginationSkip(query)      return {        data: [          { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Alice' },          { id: '550e8400-e29b-41d4-a716-446655440001', name: 'Bob' },        ],        total: 2,        skip,        limit,      }    },  }),
  create: route.post('/', {    summary: 'Create a new user',    tags: ['Users'],    body: tex.object({      name: tex.string({ min: 2 }),      email: tex.email(),    }),    responses: {      201: { description: 'User successfully created' },      400: { description: 'Validation error' },    },    async handle({ body }) {      return { id: '550e8400-e29b-41d4-a716-446655440002', ...body }    },  }),
  getById: route.get('/:id', {    summary: 'Get user by UUID',    tags: ['Users'],    params: tex.object({ id: tex.uuid() }),    responses: {      200: { description: 'User details found' },      404: { description: 'User not found' },    },    async handle({ params }) {      return { id: params.id, name: 'Alice' }    },  }),})

Documenting Class-Based Controllers (OOP)

For class-based controllers in route.ts, use OpenAPI decorators from exisjs/swagger alongside standard routing decorators:

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 list of catalog products',  })  @ApiResponse({ status: 200, description: 'List of products' })  async list(@Query('page') page: number = 1) {    return [      { id: 1, name: 'Mechanical Keyboard', price: 99 },      { id: 2, name: 'Ergonomic Mouse', price: 49 },    ]  }
  @Post('/')  @ApiOperation({ summary: 'Create new product' })  @ApiResponse({ status: 201, description: 'Product created' })  async create(@Body() dto: { name: string; price: number }) {    return { id: 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 Swagger documentation  async metrics() {    return { uptime: process.uptime() }  }}

Cascading Boundary Metadata

Cascading boundaries (boundary.ts) can assign shared tags, descriptions, or security requirements to all nested routes in the directory:

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

Security Schemes & Authentication

Define reusable security schemes (such as Bearer JWT, API Keys, or 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',      },    },    security: [      { bearerAuth: [] },    ],  },})

Advanced Configuration (exis.config.ts)

For advanced customization, such as modifying the generated OpenAPI spec before serving or injecting custom styling:

exis.config.ts
import { defineConfig } from 'exisjs/config'
export default defineConfig({  docs: {    enabled: true,    title: 'Enterprise API Gateway',    version: '2.0.0',    description: 'Internal and external microservices API reference',    ui: 'scalar', // 'swagger-ui' | 'scalar'    servers: [      { url: 'https://api.example.com', description: 'Production Server' },      { url: 'http://localhost:3000', description: 'Local Development' },    ],    transformSpec(spec) {      // Programmatically mutate or enrich OpenAPI specification      spec.info.contact = { name: 'API Support', email: '[email protected]' }      return spec    },  },})

Server Lifecycle Hook (server.ts)

You can also interact with the Swagger subsystem or application instance during server boot in src/http/server.ts:

Functional Paradigm

src/http/server.ts
import { exis } from 'exisjs'
export default exis({  onStart(app) {    console.log('[API] Swagger documentation ready at /docs')  },})

Class-Based (OOP) Paradigm

src/http/server.ts
import { Server } from 'exisjs/decorators'import type { App } from 'exisjs'
@Server()export default class RootServer {  async onStart(app: App) {    console.log('[API] Swagger documentation ready at /docs')  }}