Automatic OpenAPI 3.1 spec generation for AsenaJS — zero config, uses your existing validators.
Your existing @Controller routes and validator schemas (json(), query(), param(), response()) are automatically converted to a full OpenAPI specification. No extra annotations needed.
- Zero Config - Extracts schemas from existing validators, no extra annotations needed
- OpenAPI 3.1 - Generates JSON Schema draft-2020-12 compatible spec
- Zero Runtime Dependencies - Only peer deps (asena, reflect-metadata, zod)
- Built-in Swagger UI - CDN-based UI page, no npm install required
- @Hidden Decorator - Class and method level exclusion from spec
- Zod v4 Native - Uses
z.toJSONSchema()for accurate conversion - Pluggable Converters -
SchemaConverterinterface for custom schema types - IoC Integrated - PostProcessor pattern, auto-discovers controllers during bootstrap
- Bun v1.3.11 or higher
- @asenajs/asena v0.7.0 or higher
- Zod v4.3 or higher
bun add @asenajs/asena-openapiimport { OpenApi, OpenApiPostProcessor } from '@asenajs/asena-openapi';
@OpenApi({
info: { title: 'My API', version: '1.0.0' },
path: '/api/openapi',
ui: true, // Swagger UI at /api/openapi/ui
})
export class AppOpenApi extends OpenApiPostProcessor {}Asena automatically discovers it — that's it.
Now:
GET /api/openapi→ OpenAPI 3.1 JSON specGET /api/openapi/ui→ Swagger UI page
The OpenApiPostProcessor automatically:
- Intercepts every
@Controllerduring IoC setup - Extracts route metadata (
@Get,@Post,@Put,@Delete) - Resolves validators and converts their Zod schemas to JSON Schema
- Generates a complete OpenAPI 3.1 spec
- Registers GET endpoints on the adapter for spec and Swagger UI
Your existing validators do double duty — they validate requests AND generate documentation:
@Middleware({ validator: true })
export class CreateUserValidator extends ValidationService {
// → requestBody (application/json)
json() {
return z.object({
name: z.string().min(1),
email: z.string().email(),
});
}
// → query parameters
query() {
return z.object({
page: z.coerce.number().optional(),
});
}
// → path parameters
param() {
return z.object({
id: z.string().uuid(),
});
}
// → response schemas by status code
response() {
return {
201: z.object({ id: z.string(), name: z.string() }),
400: { schema: z.object({ error: z.string() }), description: 'Validation error' },
};
}
}@Hidden
Hide controllers or individual routes from the spec:
// Hide entire controller
@Hidden()
@Controller('/internal')
export class InternalController { ... }
// Hide single route
@Controller('/api')
export class ApiController {
@Hidden()
@Get('/health')
healthCheck() {}
@Get('/users') // this route IS in the spec
listUsers() {}
}@OpenApi({
info: {
title: 'My API', // Required
version: '1.0.0', // Required
description: 'My app', // Optional
},
path: '/api/openapi', // Default: '/openapi'
ui: true, // Default: false — enables Swagger UI at {path}/ui
servers: [
// Optional
{ url: 'https://api.example.com', description: 'Production' },
],
converters: [
// Default: [ZodSchemaConverter]
new ZodSchemaConverter(),
],
})
export class AppOpenApi extends OpenApiPostProcessor {}When ui: true, a Swagger UI page is served at {path}/ui. It loads from CDN — zero npm dependencies:
- Uses
swagger-ui-dist@5from unpkg CDN - No build step required
- Works in development and production
For manual spec generation without the PostProcessor:
import { OpenApiGenerator, ZodSchemaConverter } from '@asenajs/asena-openapi';
const generator = new OpenApiGenerator({
info: { title: 'My API', version: '1.0.0' },
converters: [new ZodSchemaConverter()],
});
const spec = await generator.generate(server.coreContainer.container);Contributions are welcome! Please follow these guidelines:
- Maintain test coverage for critical paths
- Follow existing code style and linting rules
- Test with both Hono and Ergenecore adapters
Submit a Pull Request on GitHub.
MIT
Issues or questions? Open an issue on GitHub.