Swagger

Generate OpenAPI 3.1 specs and serve Swagger UI from controller metadata.

@miiajs/swagger scans your controllers for decorator metadata and generates an OpenAPI 3.1 spec with Swagger UI.

Installation

npm install @miiajs/swagger swagger-ui-dist

Setup

import { SwaggerModule } from '@miiajs/swagger'

const app = new Miia()
  .use(cors())
  .register(AppModule)

SwaggerModule.setup(app, {
  title: 'My API',
  version: '1.0.0',
  description: 'API documentation',
  servers: [
    { url: 'http://localhost:3000', description: 'Development' },
  ],
  securitySchemes: {
    bearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
  },
})

await app.listen(3000)

Endpoints:

  • GET /docs — Swagger UI
  • GET /docs/json — OpenAPI JSON spec

Setup options

interface SwaggerSetupOptions {
  title: string                // Required
  version: string              // Required
  description?: string
  servers?: Array<{ url: string; description?: string }>
  securitySchemes?: Record<string, any>
  globalSecurity?: Array<Record<string, string[]>>
  path?: string                // Spec path (default: '/docs/json')
  uiPath?: string              // UI path (default: '/docs')
  ui?: boolean                 // Serve UI (default: true)
  swaggerOptions?: Record<string, any>
}

Decorators

@ApiTag

Group routes under tags (class-level):

@ApiTag('Users')
@Controller('/users')
class UserController {}

@ApiOperation

Describe an endpoint (method-level):

@ApiOperation({
  summary: 'Create a new user',
  description: 'Creates a user and returns the created object',
  operationId: 'createUser',
  deprecated: false,
})
@Post('/')
create(ctx: RequestContext) {}

@ApiResponse

Define response schemas (method-level, stackable):

@ApiResponse(200, {
  description: 'User found',
  schema: UserResponseSchema,  // Zod schema or JSON Schema
})
@ApiResponse(404, { description: 'User not found' })
@Get('/:id')
findOne(ctx: RequestContext) {}

@ApiParam

Document path parameters (method-level, stackable):

@ApiParam('id', {
  description: 'User ID',
  schema: { type: 'string', format: 'uuid' },
})
@Get('/:id')
findOne(ctx: RequestContext) {}

Path parameters from :paramName patterns are auto-detected.

@ApiQuery

Document query parameters (method-level, stackable):

@ApiQuery('limit', { description: 'Max results', required: false })
@ApiQuery('offset', { description: 'Pagination offset', required: false })
@Get('/')
list(ctx: RequestContext) {}

Query parameters from @ValidateQuery schemas are auto-detected.

@ApiHeader

Document request headers (class or method-level, stackable):

@ApiHeader('X-Api-Key', { description: 'API key', required: true })
@Controller('/admin')
class AdminController {}

@ApiSecurity

Declare security requirements (class or method-level):

@ApiSecurity('bearer')
@Controller('/api')
class ApiController {}

// With scopes
@ApiSecurity('oauth2', ['write:users'])
@Delete('/:id')
remove(ctx: RequestContext) {}

@ApiExclude

Hide routes or controllers from the spec:

@ApiExclude()
@Controller('/internal')
class InternalController {}

// Or exclude a single route
@ApiExclude()
@Get('/debug')
debug() {}

Auto-detection

The spec builder automatically detects:

  • Path parameters from route patterns (:id -> {id})
  • Query parameters from @ValidateQuery schemas
  • Request body from @ValidateBody schemas
  • Default responses (200/201 based on method)
  • 422 response when validation decorators are present
  • 403 response when guards are present

Schema support

Decorators accept any ZodLike schema (Zod v3, v4, or custom) and raw JSON Schema objects:

import { z } from 'zod'

const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(['user', 'admin']),
  age: z.number().int().optional(),
})

@ApiResponse(200, { schema: UserSchema })

Zod types are converted to JSON Schema automatically.

Complete example

@ApiTag('Users')
@ApiSecurity('bearer')
@Controller('/users')
@UseGuard(AuthGuard())
class UserController {
  @Get('/')
  @Public()
  @ApiOperation({ summary: 'List all users' })
  @ApiResponse(200, { schema: z.array(UserSchema) })
  list() {}

  @Get('/:id')
  @ApiOperation({ summary: 'Get user by ID' })
  @ApiParam('id', { description: 'User ObjectId' })
  @ApiResponse(200, { schema: UserSchema })
  @ApiResponse(404, { description: 'Not found' })
  findOne(ctx: RequestContext) {}

  @Post('/')
  @Status(201)
  @ValidateBody(CreateUserSchema)
  @ApiOperation({ summary: 'Create user' })
  @ApiResponse(201, { schema: UserSchema })
  create(ctx: RequestContext) {}

  @Delete('/:id')
  @ApiOperation({ summary: 'Delete user' })
  @ApiParam('id')
  @ApiResponse(204, { description: 'Deleted' })
  remove(ctx: RequestContext) {}
}

Exports

import {
  SwaggerModule,
  ApiTag,
  ApiOperation,
  ApiResponse,
  ApiParam,
  ApiQuery,
  ApiSecurity,
  ApiHeader,
  ApiExclude,
  SpecBuilder,
  convertSchema,
} from '@miiajs/swagger'