OpenAPI Plugin
Maintaining separate documentation (like a Postman collection or a manually edited YAML file) is a productivity killer. It inevitably drifts out of sync with the actual code.
Karin follows a Code-First philosophy for API documentation. You define your API contract using TypeScript Classes (DTOs) and Decorators, and the OpenApiPlugin automatically generates a comprehensive OpenAPI 3.0 Specification (formerly Swagger).
Concept: The Living Map
Manually writing docs is like drawing a map of a forest from memory. Code-First documentation is like a satellite updating the map in real-time. If you change a route in the code, the map updates immediately.
Metadata Reflection & Generation
The plugin works by scanning the application's metadata at bootstrap. It inspects:
- Router: Extracts paths (
/users), methods (GET,POST), and parameters. - DTOs: Converts Zod schemas into JSON Schema objects.
- Decorators: Reads explicit
@ApiOperation,@ApiBodytags.
It then serves a Swagger UI (by default at /docs) that is interactive and executable.
Decorators Strategy
To enrich the generated documentation beyond simple paths, you decorate your controllers.
import { Controller, Post, Body } from "@project-karin/core";
import { ApiTags, ApiOperation, ApiResponse, ApiBody } from "@project-karin/openapi";
import { CreateUserDto, UserResponseDto } from "./dtos";
@Controller("users")
@ApiTags("User Management") // Groups endpoints in the UI
export class UserController {
@Post()
@ApiOperation({
summary: "Register a new user",
description: "Creates a user account and sends a verification email."
})
@ApiBody(CreateUserDto) // Links the Zod Schema to the Request Body
@ApiResponse(201, UserResponseDto) // Links the Response Schema
@ApiResponse(409, { description: "Email already exists" })
async create(@Body() dto: CreateUserDto) {
// ...
}
}Zod Schema Integration
The most powerful feature of the OpenApiPlugin is its native understanding of Zod. You do not need to manually define property types (type: 'string'). The plugin infers them from your validation schemas.
// create-user.dto.ts
import { z } from "zod";
export const CreateUserSchema = z.object({
email: z.string().email().describe("The user's unique email address"),
age: z.number().min(18).describe("Must be legal age"),
roles: z.array(z.enum(["admin", "user"])).default(["user"])
});
// The generated OpenAPI spec will automatically include:
// - type: string, format: email
// - type: number, minimum: 18
// - enum: ["admin", "user"]
// - descriptionsSetup & Configuration
The plugin should be registered last, to ensure all other metadata has been loaded.
new OpenApiPlugin({
title: "Karin API",
version: "1.0.0",
// The path where the UI will be mounted
path: "/docs",
// Optional: Hide the Schema definition block in the UI to reduce clutter
defaultModelsExpandDepth: -1,
})Authentication Documentation
If your API is protected, you must define the security scheme so the "Authorize" button appears in Swagger UI.
new OpenApiPlugin({
// ...
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
// Apply globally by default
security: [{ bearerAuth: [] }],
})