OpenAPI

Nanoka supports generating OpenAPI 3.1 documents from model definitions and route metadata. The generated spec is documentation — it is not used for runtime validation.

toOpenAPIComponent()

Returns an OpenAPI component object for the model that can be referenced in the spec. Includes all fields with their types and descriptions, with policies applied for output.

const component = User.toOpenAPIComponent()
// { UserOutput: { type: 'object', properties: { ... } } }

toOpenAPISchema(usage)

Returns an OpenAPI schema object for a specific usage context. Three usages are available:

// Schema for create request body — readOnly and serverOnly fields excluded
User.toOpenAPISchema('create')

// Schema for update request body — readOnly and serverOnly excluded, all fields optional
User.toOpenAPISchema('update')

// Schema for response body — serverOnly and writeOnly fields excluded
User.toOpenAPISchema('output')

Use these inline in your route definitions to document request and response shapes:

app.post('/users', {
  openapi: {
    summary: 'Create user',
    requestBody: {
      required: true,
      content: { 'application/json': { schema: User.toOpenAPISchema('create') } },
    },
    responses: {
      '201': {
        description: 'Created user',
        content: { 'application/json': { schema: User.toOpenAPISchema('output') } },
      },
    },
  },
}, User.validator('json', 'create'), handler)

app.openapi(metadata)

Registers OpenAPI metadata for a route independently of the route handler. Use this form when you need c.req.valid() to retain its inferred type in the handler.

app.openapi({
  method: 'post',
  path: '/users',
  summary: 'Create user',
  requestBody: {
    required: true,
    content: { 'application/json': { schema: User.toOpenAPISchema('create') } },
  },
  responses: {
    '201': { description: 'Created user' },
  },
})

app.post('/users', User.validator('json', 'create'), async (c) => {
  const body = c.req.valid('json')  // type is properly inferred here
  const user = await User.create(body)
  return c.json(User.toResponse(user), 201)
})

Known limitation: inline { openapi } loses c.req.valid type inference

When you pass { openapi: ... } as the second argument to app.get() / app.post() / etc., the handler's c.req.valid() loses its inferred type. This is because the inline form uses a variadic H[] overload that does not thread the validator's type through to the handler.

// Inline form — c.req.valid loses type inference
app.post('/users', { openapi: { ... } }, User.validator('json', 'create'), async (c) => {
  const body = c.req.valid('json')  // type is 'unknown' here
  // Must cast explicitly:
  const body2 = (c.req.valid as (t: 'json') => { email: string; name: string })('json')
})

Recommendation: use app.openapi() + a separate route definition when full handler type inference is required.

app.generateOpenAPISpec(options)

Builds the full OpenAPI 3.1 document from all registered metadata. Mount the result at /openapi.json:

app.get('/openapi.json', (c) =>
  c.json(
    app.generateOpenAPISpec({
      info: { title: 'My API', version: '1.0.0' },
    }),
  ),
)

The info field is required. Optional servers and tags can be added.

swaggerUI({ url, title? })

Mounts a Swagger UI endpoint that reads from the given OpenAPI JSON URL:

import { swaggerUI } from '@nanokajs/core'

app.get('/docs', swaggerUI({ url: '/openapi.json', title: 'API Docs' }))

Navigate to /docs to see the interactive API documentation.

OpenAPI and runtime validation

Important: The OpenAPI spec is documentation only. Runtime validation is always performed by Zod schemas and Hono validators (inputSchema() / outputSchema() / User.validator()). Do not treat the OpenAPI spec as a security boundary.