スキーマ駆動で、Zod OpenAPI Honoによる、API開発するために、Hono Takibiというライブラリを作っている
![]()
Hono Takibi

はじめに
Hono Conference 2025で、Hono Takibiについて紹介しました。
スライド
Hono Takibiの紹介
Hono Takibiは、OpenAPI定義から、Zod OpenAPI Honoのコードを生成するツールです。
ディレクトリ構成
.
├── main.tsp
├── package.json
├── src
│ └── **
└── tsconfig.json
今回の例では、Typespecを使用して紹介します。以下をinstallする必要があります。
import "@typespec/http";
using Http;
model Hono {
value: "Hono"
}
model HonoX {
value: "HonoX"
}
model ZodOpenAPIHono {
value: "ZodOpenAPIHono"
}
@route("/hono")
@tag("Hono")
interface HonoService {
@get get(): Hono
}
@route("/honox")
@tag("HonoX")
interface HonoXService {
@get get(): HonoX
}
@route("/zod-openapi-hono")
@tag("ZodOpenAPIHono")
interface ZodOpenAPIHonoService {
@get get(): ZodOpenAPIHono
}
hono-takibiを実行します。
npx hono-takibi main.tsp -o src/routes/index.ts
以下のような、src/routes/index.tsが生成されます。
import { createRoute, z } from '@hono/zod-openapi'
const HonoSchema = z.object({ value: z.literal('Hono') }).openapi('Hono')
const HonoXSchema = z.object({ value: z.literal('HonoX') }).openapi('HonoX')
const ZodOpenAPIHonoSchema = z
.object({ value: z.literal('ZodOpenAPIHono') })
.openapi('ZodOpenAPIHono')
export const getHonoRoute = createRoute({
tags: ['Hono'],
method: 'get',
path: '/hono',
operationId: 'HonoService_get',
responses: {
200: {
description: 'The request has succeeded.',
content: { 'application/json': { schema: HonoSchema } },
},
},
})
export const getHonoxRoute = createRoute({
tags: ['HonoX'],
method: 'get',
path: '/honox',
operationId: 'HonoXService_get',
responses: {
200: {
description: 'The request has succeeded.',
content: { 'application/json': { schema: HonoXSchema } },
},
},
})
export const getZodOpenapiHonoRoute = createRoute({
tags: ['ZodOpenAPIHono'],
method: 'get',
path: '/zod-openapi-hono',
operationId: 'ZodOpenAPIHonoService_get',
responses: {
200: {
description: 'The request has succeeded.',
content: { 'application/json': { schema: ZodOpenAPIHonoSchema } },
},
},
})
出力のカスタマイズ
hono-takibi.config.tsを用意します。
import { defineConfig } from 'hono-takibi/config'
export default defineConfig({
input: 'main.tsp',
'zod-openapi': {
schema: { output: 'src/schemas/index.ts' },
route: { output: 'src/routes/index.ts', import: '../schemas' },
},
})
必ず、プロジェクトルートに配置してください。
.
├── hono-takibi.config.ts
├── main.tsp
├── package.json
├── package-lock.json
├── src
│ └── **
└── tsconfig.json
hono-takibiを実行します。
npx hono-takibi
以下のように、ファイルが生成されます。
.
├── hono-takibi.config.ts
├── main.tsp
├── package.json
├── package-lock.json
├── src
│ ├── routes
│ │ └── index.ts
│ └── schemas
│ └── index.ts
└── tsconfig.json
src/routes/index.ts
import { createRoute } from '@hono/zod-openapi'
import { HonoSchema, HonoXSchema, ZodOpenAPIHonoSchema } from '../schemas'
export const getHonoRoute = createRoute({
tags: ['Hono'],
method: 'get',
path: '/hono',
operationId: 'HonoService_get',
responses: {
200: {
description: 'The request has succeeded.',
content: { 'application/json': { schema: HonoSchema } },
},
},
})
export const getHonoxRoute = createRoute({
tags: ['HonoX'],
method: 'get',
path: '/honox',
operationId: 'HonoXService_get',
responses: {
200: {
description: 'The request has succeeded.',
content: { 'application/json': { schema: HonoXSchema } },
},
},
})
export const getZodOpenapiHonoRoute = createRoute({
tags: ['ZodOpenAPIHono'],
method: 'get',
path: '/zod-openapi-hono',
operationId: 'ZodOpenAPIHonoService_get',
responses: {
200: {
description: 'The request has succeeded.',
content: { 'application/json': { schema: ZodOpenAPIHonoSchema } },
},
},
})
src/schemas/index.ts
import { z } from '@hono/zod-openapi'
export const HonoSchema = z.object({ value: z.literal('Hono') }).openapi('Hono')
export const HonoXSchema = z.object({ value: z.literal('HonoX') }).openapi('HonoX')
export const ZodOpenAPIHonoSchema = z
.object({ value: z.literal('ZodOpenAPIHono') })
.openapi('ZodOpenAPIHono')
さらに分割したい場合は、以下のようになります。split: trueを追加し、outputをディレクトリ名にすることで、分割されます。
import { defineConfig } from 'hono-takibi/config'
export default defineConfig({
input: 'main.tsp',
'zod-openapi': {
schema: { output: 'src/schemas', split: true },
route: { output: 'src/routes', import: '../schemas', split: true },
},
})
以下のように、1ファイル1ルート、1ファイル1スキーマになります。
.
├── hono-takibi.config.ts
├── main.tsp
├── package.json
├── package-lock.json
├── src
│ ├── routes
│ │ ├── getHono.ts
│ │ ├── getHonox.ts
│ │ ├── getZodOpenapiHono.ts
│ │ └── index.ts
│ └── schemas
│ ├── hono.ts
│ ├── honoX.ts
│ ├── index.ts
│ └── zodOpenAPIHono.ts
└── tsconfig.json
src/routes/index.ts
export * from './getHono'
export * from './getHonox'
export * from './getZodOpenapiHono'
src/routes/getHono.ts
import { createRoute } from '@hono/zod-openapi'
import { HonoSchema } from '../schemas'
export const getHonoRoute = createRoute({
tags: ['Hono'],
method: 'get',
path: '/hono',
operationId: 'HonoService_get',
responses: {
200: {
description: 'The request has succeeded.',
content: { 'application/json': { schema: HonoSchema } },
},
},
})
他のルートファイルも同様です。
src/schemas/index.ts
export * from './hono'
export * from './honoX'
export * from './zodOpenAPIHono'
src/schemas/hono.ts
import { z } from '@hono/zod-openapi'
export const HonoSchema = z.object({ value: z.literal('Hono') }).openapi('Hono')
他のスキーマファイルも同様です。
RPCの生成
clientのパスをimportで合わせる必要があります。
type AddType = typeof api
export const client = hc<AddType>('/')
以下のように設定します。
import { defineConfig } from 'hono-takibi/config'
export default defineConfig({
input: 'main.tsp',
rpc: {
output: 'src/rpc/index.ts',
import: '../index',
},
})
src/rpc/index.ts
import { client } from '../index'
/**
* GET /hono
*/
export async function getHono() {
return await client.hono.$get()
}
/**
* GET /honox
*/
export async function getHonox() {
return await client.honox.$get()
}
/**
* GET /zod-openapi-hono
*/
export async function getZodOpenapiHono() {
return await client['zod-openapi-hono'].$get()
}
split: trueにし、分割も可能です。
import { defineConfig } from 'hono-takibi/config'
export default defineConfig({
input: 'main.tsp',
rpc: {
output: 'src/rpc',
import: '../index',
split: true,
},
})
以下のように、1ファイル1RPCになります。
.
├── hono-takibi.config.ts
├── main.tsp
├── package.json
├── package-lock.json
├── src
│ ├── handlers
│ │ └── **
│ ├── index.ts
│ ├── routes
│ │ └── **
│ ├── rpc
│ │ ├── getHono.ts
│ │ ├── getHonox.ts
│ │ ├── getZodOpenapiHono.ts
│ │ └── index.ts
│ └── schemas
│ └── **
└── tsconfig.json
Viteのと組み合わせ
hono-takibi.config.tsの設定が利用されます。Vite起動時、API定義を編集すると、コードが自動で更新されます。
import { honoTakibiVite } from 'hono-takibi/vite-plugin'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [honoTakibiVite()],
})
Demo

おわりに
Hono Conference 2025の発表内容の記事でした。ぜひ、hono-takibiを試してみてください。
Discussion