OrvalOrval

Hono

Generate Hono server templates from OpenAPI

Generate Hono server templates with full validation from your OpenAPI specification.

Configuration

Set the client option to hono:

orval.config.ts
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    input: {
      target: './petstore.yaml',
    },
    output: {
      mode: 'split',
      client: 'hono',
      target: 'src/petstore.ts',
      override: {
        hono: {
          handlers: 'src/handlers',
        },
      },
    },
  },
});

Generated Files

src/
├── handlers/
│   ├── createPets.ts
│   ├── listPets.ts
│   ├── showPetById.ts
│   └── updatePets.ts
├── petstore.ts           # Hono app with routes
├── petstore.context.ts   # Type-safe context
├── petstore.schemas.ts   # Request/response schemas
├── petstore.validator.ts # Hono validator
└── petstore.zod.ts       # Zod schemas

Handler Template

Orval generates handler templates with built-in validation:

src/handlers/listPets.ts
/**
 * Generated by orval 🍺
 * Do not edit manually.
 * Swagger Petstore
 * OpenAPI spec version: 1.0.0
 */
import { createFactory } from 'hono/factory';
import { zValidator } from '../petstore.validator';
import { ListPetsContext } from '../petstore.context';
import { ListPetsQueryParams, listPetsResponse } from '../petstore.zod';

const factory = createFactory();

export const listPetsHandlers = factory.createHandlers(
  zValidator('query', ListPetsQueryParams),
  zValidator('response', listPetsResponse),
  async (c: ListPetsContext) => {
    // Implement your logic here
  },
);

Implementing Handlers

Add your business logic to the generated handlers:

Re-running orval reconciles your handler files according to override.hono.handlerGenerationStrategy (default smart). With smart, orval only updates the parts it owns — its own imports (names, paths, casing) and the zValidator(...) arguments, and appends handlers for new operations — while preserving your custom imports, middleware, handler bodies, and top-level helpers. Use skip to freeze a file, or full for the legacy behavior that rebuilds the wrapper and keeps only the body.

src/handlers/listPets.ts
export const listPetsHandlers = factory.createHandlers(
  zValidator('query', ListPetsQueryParams),
  zValidator('response', listPetsResponse),
  async (c: ListPetsContext) => {
    return c.json([
      { id: 1, name: 'Buddy' },
      { id: 2, name: 'Max' },
    ]);
  },
);

Running the Server

wrangler dev src/petstore.ts
curl http://localhost:8787/pets
# => [{"id":1,"name":"Buddy"},{"id":2,"name":"Max"}]

Composite Routes (tags-split)

For larger APIs, use tags-split mode with composite routes:

orval.config.ts
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    input: {
      target: './petstore.yaml',
    },
    output: {
      mode: 'tags-split',
      client: 'hono',
      target: 'src/endpoints',
      schemas: 'src/schemas',
      override: {
        hono: {
          compositeRoute: 'src/routes.ts',
        },
      },
    },
  },
});

This generates a combined routes file:

src/routes.ts
import { Hono } from 'hono';
import {
  listPetsHandlers,
  createPetsHandlers,
  showPetByIdHandlers,
} from './endpoints/pets/pets.handlers';

const app = new Hono();

app.get('/pets', ...listPetsHandlers);
app.post('/pets', ...createPetsHandlers);
app.get('/pets/:petId', ...showPetByIdHandlers);

export default app;

Full Examples

On this page