Hono Takibi 0.2.0 🔥 z.infer などの対応
はじめに
Hono Advent Calendar 2024シリーズ2の5日目の記事です。
前回の記事では、Hono Takibi
を紹介しました。
npm
GitHub
Hono Takibi 0.2.0
Hono Takibi
の0.2.0
では、設定ファイルを使用して、生成されるコードの形式をカスタマイズできるようになりました。また、zod-to-openapi
のopenapi
メソッドのサポートが一部追加され、OpenAPI仕様に準拠したスキーマ定義の出力が可能になりました。
本記事では、これらの新機能と使用方法について詳しく解説します。
参考
Installation
npm install -D hono-takibi
Usage
- OpenAPI定義ファイル(YAMLまたはJSONファイル)を用意します。
.
└── openapi.yaml
openapi.yaml
の内容を以下に示します(このサンプルは生成AIを使用して作成しています)。
openapi.yaml
openapi: 3.0.3
info:
title: Sample API
description: >
これはサンプルのREST APIです。
OpenAPI 3.0以降に対応した仕様書の例としてご利用ください。
version: "1.0.0"
servers:
- url: https://api.example.com/v1
description: 本番環境
- url: https://stg-api.example.com/v1
description: ステージング環境
paths:
/users:
get:
summary: ユーザー一覧取得
description: すべてのユーザーの一覧を取得します。
tags:
- Users
responses:
'200':
description: ユーザー一覧の取得に成功
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
'400':
description: 不正なリクエスト
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: ユーザー登録
description: 新規ユーザーを登録します。
tags:
- Users
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserCreateRequest"
responses:
'201':
description: 新規ユーザー作成に成功
content:
application/json:
schema:
$ref: "#/components/schemas/User"
'400':
description: 不正なリクエスト
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/users/{userId}:
parameters:
- in: path
name: userId
schema:
type: string
required: true
description: 取得または更新するユーザーのID
get:
summary: 特定ユーザー取得
description: 指定したユーザーIDのユーザー情報を取得します。
tags:
- Users
responses:
'200':
description: ユーザー情報の取得に成功
content:
application/json:
schema:
$ref: "#/components/schemas/User"
'404':
description: ユーザーが見つからない
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
put:
summary: ユーザー更新
description: 指定したユーザーの情報を更新します。
tags:
- Users
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserUpdateRequest"
responses:
'200':
description: ユーザー情報の更新に成功
content:
application/json:
schema:
$ref: "#/components/schemas/User"
'400':
description: 不正なリクエスト
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
'404':
description: ユーザーが見つからない
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
delete:
summary: ユーザー削除
description: 指定したユーザーを削除します。
tags:
- Users
responses:
'204':
description: ユーザー削除に成功 (レスポンスボディなし)
'404':
description: ユーザーが見つからない
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
User:
type: object
properties:
id:
type: string
description: ユーザーID
example: "12345"
name:
type: string
description: ユーザーの名前
example: "Taro Yamada"
email:
type: string
description: メールアドレス
example: "taro.yamada@example.com"
createdAt:
type: string
format: date-time
description: ユーザー作成日
example: "2024-01-01T12:34:56Z"
required:
- id
- name
- email
- createdAt
UserCreateRequest:
type: object
properties:
name:
type: string
description: ユーザーの名前
example: "Hanako Suzuki"
email:
type: string
format: email
description: メールアドレス
example: "hanako.suzuki@example.com"
password:
type: string
format: password
description: パスワード
example: "mypassword123"
required:
- name
- email
- password
UserUpdateRequest:
type: object
properties:
name:
type: string
description: 新しいユーザーの名前
example: "Jiro Tanaka"
email:
type: string
format: email
description: 新しいメールアドレス
example: "jiro.tanaka@example.com"
required:
- name
- email
Error:
type: object
properties:
code:
type: integer
description: エラーコード
example: 404
message:
type: string
description: エラーメッセージ
example: "Not Found"
required:
- code
- message
hono-takibi
を実行します。
npx hono-takibi openapi.yaml -o index.ts
.
├── index.ts
└── openapi.yaml
以下のような、index.ts
が生成されます。
import { createRoute, z } from '@hono/zod-openapi'
const userSchema = z
.object({
id: z.string().openapi({ example: '12345' }),
name: z.string().openapi({ example: 'Taro Yamada' }),
email: z.string().openapi({ example: 'taro.yamada@example.com' }),
createdAt: z.string().datetime().openapi({ example: '2024-01-01T12:34:56Z' }),
})
.openapi('User')
const userCreateRequestSchema = z
.object({
name: z.string().openapi({ example: 'Hanako Suzuki' }),
email: z.string().email().openapi({ example: 'hanako.suzuki@example.com' }),
password: z.string().openapi({ example: 'mypassword123' }),
})
.openapi('UserCreateRequest')
const userUpdateRequestSchema = z
.object({
name: z.string().openapi({ example: 'Jiro Tanaka' }),
email: z.string().email().openapi({ example: 'jiro.tanaka@example.com' }),
})
.openapi('UserUpdateRequest')
const errorSchema = z
.object({
code: z.number().int().openapi({ example: 404 }),
message: z.string().openapi({ example: 'Not Found' }),
})
.openapi('Error')
export const getUsersRoute = createRoute({
tags: ['Users'],
method: 'get',
path: '/users',
summary: 'ユーザー一覧取得',
description: 'すべてのユーザーの一覧を取得します。',
responses: {
200: {
description: 'ユーザー一覧の取得に成功',
content: { 'application/json': { schema: z.array(userSchema) } },
},
400: {
description: '不正なリクエスト',
content: { 'application/json': { schema: errorSchema } },
},
},
})
export const postUsersRoute = createRoute({
tags: ['Users'],
method: 'post',
path: '/users',
summary: 'ユーザー登録',
description: '新規ユーザーを登録します。',
request: {
body: { required: true, content: { 'application/json': { schema: userCreateRequestSchema } } },
},
responses: {
201: {
description: '新規ユーザー作成に成功',
content: { 'application/json': { schema: userSchema } },
},
400: {
description: '不正なリクエスト',
content: { 'application/json': { schema: errorSchema } },
},
},
})
export const getUsersUserIdRoute = createRoute({
tags: ['Users'],
method: 'get',
path: '/users/{userId}',
summary: '特定ユーザー取得',
description: '指定したユーザーIDのユーザー情報を取得します。',
responses: {
200: {
description: 'ユーザー情報の取得に成功',
content: { 'application/json': { schema: userSchema } },
},
404: {
description: 'ユーザーが見つからない',
content: { 'application/json': { schema: errorSchema } },
},
},
})
export const putUsersUserIdRoute = createRoute({
tags: ['Users'],
method: 'put',
path: '/users/{userId}',
summary: 'ユーザー更新',
description: '指定したユーザーの情報を更新します。',
request: {
body: { required: true, content: { 'application/json': { schema: userUpdateRequestSchema } } },
},
responses: {
200: {
description: 'ユーザー情報の更新に成功',
content: { 'application/json': { schema: userSchema } },
},
400: {
description: '不正なリクエスト',
content: { 'application/json': { schema: errorSchema } },
},
404: {
description: 'ユーザーが見つからない',
content: { 'application/json': { schema: errorSchema } },
},
},
})
export const deleteUsersUserIdRoute = createRoute({
tags: ['Users'],
method: 'delete',
path: '/users/{userId}',
summary: 'ユーザー削除',
description: '指定したユーザーを削除します。',
responses: {
204: { description: 'ユーザー削除に成功 (レスポンスボディなし)' },
404: {
description: 'ユーザーが見つからない',
content: { 'application/json': { schema: errorSchema } },
},
},
})
Hono Takibi 0.2.0 の新機能
Hono Takibi 0.2.0 では、以下の新機能を紹介します。
ディレクトリ構造
.
├── hono-takibi.json
├── index.ts
└── openapi.yaml
設定ファイル
hono-takibi.json
は、hono-takibi
の設定ファイルです。
{
"schemaOptions": {
"namingCase": "camelCase",
"exportEnabled": false
},
"typeOptions": {
"namingCase": "PascalCase",
"exportEnabled": false
}
}
オプション
Option | Type | Default | Description |
---|---|---|---|
namingCase |
"camelCase" | "PascalCase"
|
"camelCase" |
生成されるスキーマ変数の命名規則 |
exportEnabled |
boolean |
false |
スキーマ定義をエクスポートするかどうか |
Type Options
Option | Type | Default | Description |
---|---|---|---|
namingCase |
"camelCase" | "PascalCase"
|
"PascalCase" |
生成される型定義の命名規則 |
exportEnabled |
boolean |
false |
型定義をエクスポートするかどうか |
オプションを指定して実行
設定ファイルでオプションを指定することで、生成されるコードの形式をカスタマイズできます。以下は設定例です。
hono-takibi.json
{
"schemaOptions": {
"namingCase": "PascalCase",
"exportEnabled": true
},
"typeOptions": {
"namingCase": "PascalCase",
"exportEnabled": true
}
}
以下のようなindex.ts
が生成されます。
import { createRoute, z } from '@hono/zod-openapi'
const UserSchema = z
.object({
id: z.string().openapi({ example: '12345' }),
name: z.string().openapi({ example: 'Taro Yamada' }),
email: z.string().openapi({ example: 'taro.yamada@example.com' }),
createdAt: z.string().datetime().openapi({ example: '2024-01-01T12:34:56Z' }),
})
.openapi('User')
const UserCreateRequestSchema = z
.object({
name: z.string().openapi({ example: 'Hanako Suzuki' }),
email: z.string().email().openapi({ example: 'hanako.suzuki@example.com' }),
password: z.string().openapi({ example: 'mypassword123' }),
})
.openapi('UserCreateRequest')
const UserUpdateRequestSchema = z
.object({
name: z.string().openapi({ example: 'Jiro Tanaka' }),
email: z.string().email().openapi({ example: 'jiro.tanaka@example.com' }),
})
.openapi('UserUpdateRequest')
const ErrorSchema = z
.object({
code: z.number().int().openapi({ example: 404 }),
message: z.string().openapi({ example: 'Not Found' }),
})
.openapi('Error')
export const schemas = {
UserSchema,
UserCreateRequestSchema,
UserUpdateRequestSchema,
ErrorSchema,
}
export type User = z.infer<typeof UserSchema>
export type UserCreateRequest = z.infer<typeof UserCreateRequestSchema>
export type UserUpdateRequest = z.infer<typeof UserUpdateRequestSchema>
export type Error = z.infer<typeof ErrorSchema>
export const getUsersRoute = createRoute({
tags: ['Users'],
method: 'get',
path: '/users',
summary: 'ユーザー一覧取得',
description: 'すべてのユーザーの一覧を取得します。',
responses: {
200: {
description: 'ユーザー一覧の取得に成功',
content: { 'application/json': { schema: z.array(UserSchema) } },
},
400: {
description: '不正なリクエスト',
content: { 'application/json': { schema: ErrorSchema } },
},
},
})
export const postUsersRoute = createRoute({
tags: ['Users'],
method: 'post',
path: '/users',
summary: 'ユーザー登録',
description: '新規ユーザーを登録します。',
request: {
body: { required: true, content: { 'application/json': { schema: UserCreateRequestSchema } } },
},
responses: {
201: {
description: '新規ユーザー作成に成功',
content: { 'application/json': { schema: UserSchema } },
},
400: {
description: '不正なリクエスト',
content: { 'application/json': { schema: ErrorSchema } },
},
},
})
export const getUsersUserIdRoute = createRoute({
tags: ['Users'],
method: 'get',
path: '/users/{userId}',
summary: '特定ユーザー取得',
description: '指定したユーザーIDのユーザー情報を取得します。',
responses: {
200: {
description: 'ユーザー情報の取得に成功',
content: { 'application/json': { schema: UserSchema } },
},
404: {
description: 'ユーザーが見つからない',
content: { 'application/json': { schema: ErrorSchema } },
},
},
})
export const putUsersUserIdRoute = createRoute({
tags: ['Users'],
method: 'put',
path: '/users/{userId}',
summary: 'ユーザー更新',
description: '指定したユーザーの情報を更新します。',
request: {
body: { required: true, content: { 'application/json': { schema: UserUpdateRequestSchema } } },
},
responses: {
200: {
description: 'ユーザー情報の更新に成功',
content: { 'application/json': { schema: UserSchema } },
},
400: {
description: '不正なリクエスト',
content: { 'application/json': { schema: ErrorSchema } },
},
404: {
description: 'ユーザーが見つからない',
content: { 'application/json': { schema: ErrorSchema } },
},
},
})
export const deleteUsersUserIdRoute = createRoute({
tags: ['Users'],
method: 'delete',
path: '/users/{userId}',
summary: 'ユーザー削除',
description: '指定したユーザーを削除します。',
responses: {
204: { description: 'ユーザー削除に成功 (レスポンスボディなし)' },
404: {
description: 'ユーザーが見つからない',
content: { 'application/json': { schema: ErrorSchema } },
},
},
})
schemaOptions
schemaOptions
のexportEnabled
をtrue
にしたので、schemas
をエクスポート。
⚠️ 重要な注意点
Hono Takibi
の0.2.0
以降では、schemas
のエクスポートは、schemaOptions
のexportEnabled
をtrue
にした場合のみ有効です。
export const schemas = {
UserSchema,
UserCreateRequestSchema,
UserUpdateRequestSchema,
ErrorSchema,
}
typeOptions
typeOptions
のexportEnabled
をtrue
にしたので、z.infer<typeof ***>
などの、型定義をエクスポート。
export type User = z.infer<typeof UserSchema>
export type UserCreateRequest = z.infer<typeof UserCreateRequestSchema>
export type UserUpdateRequest = z.infer<typeof UserUpdateRequestSchema>
export type Error = z.infer<typeof ErrorSchema>
その他の改善点
zod-to-openapi
のopenapi
メソッドのサポートが一部追加され、OpenAPI仕様に準拠したスキーマ定義の出力が可能になりました。
おわりに
Hono Takibi
の0.2.0
では、設定ファイルを使用して、生成されるコードの形式をカスタマイズできるようになりました。また、zod-to-openapi
のopenapi
メソッドのサポートが一部追加され、OpenAPI仕様に準拠したスキーマ定義の出力が可能になりました。
これらの改善により、Hono Takibi
の機能がさらに向上し、開発者のニーズに合わせたコード生成が可能になりました。今後はテストの拡充とドキュメントの整備を進めながら、継続的にメンテナンスを行い、より安定した機能を提供していく予定です。
Discussion