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.

Relations in OpenAPI Output

When a model has relations defined, pass { with } to toOpenAPISchema('output') to expand relation fields in the spec. This is spec-only — runtime validation source of truth remains Zod.

// Expand posts array in the User output schema
User.toOpenAPISchema('output', { with: { posts: true } })
// → { type: 'object', properties: { id, name, ..., posts: { type: 'array', items: { ... } } } }

// Expand author object in the Post output schema
Post.toOpenAPISchema('output', { with: { author: true } })
// → { type: 'object', properties: { id, title, ..., author: { type: 'object', nullable: true, ... } } }
  • hasMany relations expand as { type: 'array', items: { ... } }.
  • belongsTo relations expand as { type: 'object', nullable: true, ... }.
  • When with is not passed, relation fields are excluded from the schema (default behavior).

Important: The with option in toOpenAPISchema affects the spec only. Runtime validation always uses Zod. See Relations for the full Relations API.

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.