KosmoJS derives an OpenAPI 3.1 specification directly from your route definitions. Route structure, TypeScript types, VRefine constraints, parameters, responses - all reflected in the spec automatically. No manual schema authoring, no annotation layers.
Enable OpenAPI
Simply add it to your source folder's kosmo.config.ts:
import { defineConfig } from "@kosmojs/dev";
export default defineConfig({
backend: {
stack: "hono",
base: "/api",
openapi: {
outfile: "openapi.json",
openapi: "3.1.0",
info: { title: "My API", version: "1.0.0" },
servers: [{ url: "https://api.example.com/api" }],
},
},
});Configuration
backend.openapi is the one block whose options are mandatory. For how it sits alongside the rest of the folder config, see the configuration reference.
Required Options
outfile - Path where the spec is written, relative to your kosmo.config.ts.
openapi - OpenAPI version. Use "3.1.0" or any 3.1.x version.
info - API metadata:
title(required) - Name of your APIversion(required) - API version, use semantic versioning
servers - Array of server objects:
url(required) - URL the API is served from, including thebackend.baseprefix. Paths in the spec are relative to this, so getting it wrong is the usual cause of a spec whose endpoints 404 - see Server URLs and Route Pathsdescription(optional) - Human-readable label
Optional Info Properties
summary - One-line summary
description - Detailed description, supports markdown
termsOfService - URL to terms of service
contact - name, url, email
license - name (required), identifier (SPDX), url
Complete Example
const openapiConfig = {
outfile: "openapi.json",
openapi: "3.1.0",
info: {
title: "My SaaS API",
version: "2.1.0",
summary: "RESTful API for My SaaS Platform",
description: `
# API Documentation
This API provides access to all platform features including
user management, billing, and analytics.`,
termsOfService: "https://myapp.com/terms",
contact: {
name: "API Support",
url: "https://myapp.com/support",
email: "api@myapp.com",
},
license: {
name: "Apache 2.0",
url: "https://www.apache.org/licenses/LICENSE-2.0.html",
},
},
// this folder has backend.base "/api", so the dev server carries that prefix -
// in production the API is deployed at the root of its own host and carries none.
servers: [
{ url: "http://localhost:4556/api", description: "Development server" },
{ url: "https://staging-api.myapp.com", description: "Staging environment" },
{ url: "https://api.myapp.com", description: "Production server" },
],
};Server URLs and Route Paths
Paths in the spec are route names, exactly as they appear under api/. A route at api/users/[id]/index.ts becomes /users/{id}, and the index route becomes / - neither carries the folder's backend.base.
That is deliberate, not an omission. In OpenAPI, paths are relative to servers, and the prefix an API answers on is a deployment decision rather than a property of the route.
The same backend may sit behind /api in development, at the root of a dedicated host in production, and under /v2/api behind a gateway - so the prefix belongs to the server entry, and the paths stay the same in all three.
This is why servers is mandatory: it is the only place the prefix is recorded. Give each entry the full prefix, origin plus backend.base:
// folder with backend.base "/api"
servers: [
{ url: "http://localhost:4556/api", description: "Development server" },
// deployed at the root of its own host - no prefix to add
{ url: "https://api.myapp.com", description: "Production server" },
];// folder with backend.base "/admin/api"
servers: [
{ url: "http://localhost:4556/admin/api", description: "Development server" },
{ url: "https://myapp.com/admin/api", description: "Production server" },
];A client built from the spec resolves /users/{id} against whichever server it is pointed at, requesting http://localhost:4556/api/users/42 in development and https://api.myapp.com/users/42 in production - one spec, no per-environment rebuilds.
TIP
If Try it out in Swagger UI returns 404, check the server URL first. A missing backend.base prefix is the usual cause.
Derived Specification
The output is a complete OpenAPI 3.1 document covering:
- Paths - all routes with HTTP methods, parameters, request bodies, and responses
- Schemas - type definitions extracted from your
TypeScripttypes and validation schemas - Parameters - path, query, and header parameters with types and constraints
- Request Bodies - payload schemas for POST, PUT, and PATCH endpoints
- Responses - response schemas with status codes and content types
- Validation Rules -
VRefineconstraints appear as JSON Schema keywords
Path Variations for Optional Parameters
OpenAPI requires all path parameters to be mandatory, so routes with optional parameters produce multiple paths.
For a route at users/[id]/posts/{postId}/index.ts, the spec contains:
/users/{id}/posts/{postId}- full path with optional parameter present/users/{id}/posts- path without optional parameter
Both reference the same handlers and schemas.
Live Updates
The spec is recomputed automatically whenever you modify route definitions, types, or validation schemas. This happens in the background alongside validation and fetch derivation - no manual rebuild step required.
Serve the spec with any standard tooling: Swagger UI, Redoc, or Stoplight Elements.