Schema & Validator

Nanoka provides two related but separate APIs for working with Zod schemas: schema() / inputSchema() / outputSchema() return standalone Zod objects, while validator() wraps them as Hono middleware.

schema(opts?)

Returns a standalone Zod schema derived from the model's fields. Can be used without Hono — in tests, background workers, or when composing schemas.

// Full schema
const FullSchema = User.schema()

// Narrowed with pick
const NameEmailSchema = User.schema({ pick: (f) => [f.name, f.email] })

// Partial update shape
const PatchSchema = User.schema({ partial: true, omit: (f) => [f.passwordHash] })

// Standalone parse — no Hono required
const result = NameEmailSchema.safeParse(body)

validator(target, opts | preset, hook?)

Returns a Hono middleware that validates the specified request target using the model's schema.

// Custom options
app.post('/users', User.validator('json', { omit: (f) => [f.passwordHash] }), async (c) => {
  const body = c.req.valid('json')
  // body is typed — passwordHash excluded
})

// With a custom hook for error handling
app.post(
  '/users',
  User.validator('json', 'create', (result, c) => {
    if (!result.success) return c.json({ error: result.error.flatten() }, 400)
  }),
  handler,
)

Presets

Two presets are available as shorthand for common input shapes:

'create' — applies inputSchema('create'): removes readOnly and serverOnly fields.

app.post('/users', User.validator('json', 'create'), async (c) => {
  const body = c.req.valid('json')
  // id, createdAt (readOnly) and passwordHash (serverOnly) are excluded
})

'update' — applies inputSchema('update'): removes readOnly and serverOnly fields, then makes all remaining fields optional (partial: true).

app.patch('/users/:id', User.validator('json', 'update'), async (c) => {
  const body = c.req.valid('json')
  // all fields optional; readOnly and serverOnly excluded
})

Custom Options

When presets are not enough, pass explicit options:

interface SchemaOptions {
  pick?:    string[] | ((f: FieldAccessor) => string[])
  omit?:    string[] | ((f: FieldAccessor) => string[])
  partial?: boolean
}

Operations are applied in order: pick → omit → partial.

// Accept only name and email, both optional
const opts = { pick: (f) => [f.name, f.email], partial: true }
app.patch('/profile', User.validator('json', opts), handler)

Field Accessor

Both pick and omit accept a function (f) => [f.fieldName] where f maps every field name to itself as a string literal. This turns typos into compile-time type errors.

// Typo is a type error
User.schema({ pick: (f) => [f.nme] })
//                          ^^^^ Property 'nme' does not exist on type ...

// Correct
User.schema({ pick: (f) => [f.name] })

hook parameter

The optional third argument to validator() is passed directly to @hono/zod-validator. Use it to customize the error response without replacing the entire middleware.

const hook = (result, c) => {
  if (!result.success) return c.json({ errors: result.error.flatten().fieldErrors }, 422)
}

app.post('/users', User.validator('json', 'create', hook), handler)

Using a hook is also the recommended way to prevent schema leak: the default @hono/zod-validator error includes the full Zod error tree, which may reveal field names that should be private.

inputSchema('create' | 'update', opts?)

Returns a Zod schema with policies applied for the given input context.

  • 'create': removes serverOnly and readOnly fields.
  • 'update': removes serverOnly and readOnly fields, then applies partial: true.

Additional opts are applied after policy filtering.

// Extend with a custom field
const CreateBody = User.inputSchema('create').extend({
  password: z.string().min(8),
})

outputSchema(opts?)

Returns a Zod schema with policies applied for response output.

  • serverOnly fields are excluded.
  • writeOnly fields are excluded.
  • readOnly fields are included (they are safe to return).
// Parse an array of raw DB rows into safe response objects
const rows = await app.db.select().from(User.table)
const response = z.array(User.outputSchema()).parse(rows)

Policy application matrix

The table below shows which policies are included or excluded by each method:

Method serverOnly writeOnly readOnly
schema() included included included
inputSchema('create') excluded included excluded
inputSchema('update') excluded included excluded
outputSchema() excluded excluded included
validator('json', 'create') excluded included excluded
validator('json', 'update') excluded included excluded