From bf2281e486c44a0847bc5c6dfc30c3cabf61243e Mon Sep 17 00:00:00 2001 From: miyaji255 <84168445+miyaji255@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:24:58 +0900 Subject: [PATCH 1/2] add BLOG hono typia openapi --- .../blogs/2024-12-15-hono-typia-openapi.md | 267 ++++++++++++++++++ src/content/tags/hono.json | 15 + src/content/tags/hono.svg | 7 + src/content/tags/openapi.json | 14 + src/content/tags/typia.json | 15 + src/content/tags/typia.png | Bin 0 -> 8344 bytes 6 files changed, 318 insertions(+) create mode 100644 src/content/blogs/2024-12-15-hono-typia-openapi.md create mode 100644 src/content/tags/hono.json create mode 100644 src/content/tags/hono.svg create mode 100644 src/content/tags/openapi.json create mode 100644 src/content/tags/typia.json create mode 100644 src/content/tags/typia.png diff --git a/src/content/blogs/2024-12-15-hono-typia-openapi.md b/src/content/blogs/2024-12-15-hono-typia-openapi.md new file mode 100644 index 0000000..80ce492 --- /dev/null +++ b/src/content/blogs/2024-12-15-hono-typia-openapi.md @@ -0,0 +1,267 @@ +--- +title: Hono + Typia で OpenAPI ドキュメントを生成する +description: Hono + Typia で作成した Hono の型から OpenAPI ドキュメントを生成するライブラリを作りました。 +category: tech +author: miyaji +tags: [advent-calendar, javascript, typescript, openapi, hono, typia] +--- + +この記事は、[OUCC Advent Calendar 2024](https://adventar.org/calendars/10655) の 15 日目の記事です。昨日は watamario さんの [AtCoder Beginners Selection の Shift only を x86 の bsf 命令で解く](/blog/articles/2024-12-14-bsf/) でした。本日は、私が作成したHono + Typia で作成した Hono の型から OpenAPI ドキュメントを生成するライブラリについて説明します。 + +作成したライブラリはこちらです。 + +https://github.com/miyaji255/hono-typia-openapi + +## 動機 + +Hono には [@honojs/zod-openapi](https://hono.dev/examples/zod-openapi) というライブラリがあり、これを利用することでOpenAPIドキュメントを生成することができます。 + +しかし、このライブラリはその名の通りZodにしか対応しておらず、書き方もHonoから大きく変えることになり使いづらいです。TypiaはZodよりも高速なので[^1]、できることならばTypiaを使いたいところです。そこで、Honoの持つSchemaの型からOpenAPIドキュメントを生成するライブラリを作成しました。 + +また、型から生成することにより完全なゼロランタイムでOpenAPIドキュメントを生成することができます。 + +ちなみに、同じように @honojs/zod-openapi が使いづらいということで [Hono OpenAPI](https://github.com/rhinobase/hono-openapi) というライブラリも作成されています。これは Zod の他にも Valibot, Ark, TypeBox に対応していますが、Typia には対応していません。 + +## 使い方 + +CLIとPluginの2つの使い方がありますが、基本的にPluginで使うことを想定しています。 + +### インストール + +```bash +npm install hono-typia-openapi +``` + +### Plugin + +unpluginを使用して作成しているのでunpluginがサポートするフレームワーク[^2]であれば利用することができます。ここではesbuildを使った簡単な例を示します。 + +```typescript +import { build } from 'esbuild'; +import HonoTypiaOpenAPIPLugin from 'hono-typia-openapi/esbuild'; + +await build({ + entryPoints: ['src/index.ts'], + bundle: true, + outfile: 'dist/index.js', + plugins: [ + HonoTypiaOpenAPIPLugin({ + title: "My App", + appFile: `${import.meta.dirname}/src/app.ts`, + }), + ], +}) +``` + +APIでは`AppType`というHonoの型をエクスポートします。この型を使ってOpenAPIドキュメントを生成します。 + +```typescript +// src/app.ts +import { Hono } from 'hono'; + +const app = new Hono() + .get('/hello', c => c.json({ message: 'Hello, World!' })); + +export type AppType = typeof app; +export default app; +``` + +引数に取る設定は次のとおりです。 + +```typescript +interface HtoConfig { + /** + * APIのタイトル + * Info Object の title に対応します。 + * https://spec.openapis.org/oas/v3.1.0#info-object + */ + title: string; + + /** + * OpenAPI のバージョンです。 + * @default "3.1" + */ + openapi: "3.1" | "3.0"; + + /** + * APIの説明 + * Info Object の description に対応します。 + * https://spec.openapis.org/oas/v3.1.0#info-object + */ + description: string; + + /** + * APIのバージョン + * Info Object の version に対応します。 + * https://spec.openapis.org/oas/v3.1.0#info-object + * @default "1.0.0" + */ + version: string; + + /** + * Hono app のファイルパス + * このファイルにある Hono app の型を使用して OpenAPI ドキュメントを生成します。 + */ + appFile: string; + + /** + * Hono app の型名 + * appFile にある Hono app の型名です。 + * @default "AppType" + */ + appType: string; + + /** + * 出力先のファイルパス + * @default "openapi.json" + */ + output?: string; + + /** + * tsconfig のファイルパス + * デフォルトでは カレントディレクトリから親ディレクトリを探索して見つかった tsconfig.json を使用します。 + */ + tsconfig?: string; + + /** + * watch モード + * @default false + */ + watchMode?: boolean; +} +``` + +### CLI + +CLIでは`hto`コマンドを使用します。 + +```bash +npx hto --title "My App" --app-file src/app.ts +``` + +設定はPluginと同じで、それぞれ次のように対応しています。 + +|CLI オプション|Plugin オプション| +|---|---| +|`-t`, `--title`|`title`| +|`-O`, `--openapi`|`openapi`| +|`-d`, `--description`|`description`| +|`-V`, `--app-version`|`version`| +|`-a`, `--app-file`|`appFile`| +|`-n`, `--app-type`|`appType`| +|`-o`, `--output`|`output`| +|`--tsconfig`|`tsconfig`| +|`-h`, `--help`|使用方法を表示します| +|`-v`, `--version`|バージョンを表示します| + +CLIを使用する場合は設定をファイルで指定することができます。 +サポートしているファイル形式は`js`, `mjs`, `cjs`, `ts`, `json`, `yaml`, `yml`です。 +また、`package.json`に`hto`フィールドを追加することで設定を指定することもできます。 + +```javascript +// hto.config.mjs +import { defineConfig } from 'hono-typia-openapi/config'; + +export default defineConfig({ + title: "My App", + appFile: `${import.meta.dirname}/src/app.ts`, +}); +``` + +### Hono app の作成方法 + +Hono app は`@honojs/typia-validator`を使用することで自動的に型が指定されます。 + +注意事項としてはメソッドチェーンの形式で書かないと型が正しく扱われないことです。これは Hono Client も同様なのですが、メソッドチェーンにしないと変数の型がスキーマを表す型にならないためです。 + +逆にこれを利用することでスキーマに出力しないエンドポイントを作ることもできます。 + +```typescript +import { Hono } from 'hono'; +import { typiaValidator } from '@honojs/typia-validator/http'; +import typia, { type tags } from 'typia'; + +interface User { + id: number & tags.Type<'uint32'>; + name: string & tags.MaxLength<255>; + age: number & tags.Type<'uint32'> & tags.Maximum<150>; +} + +const app = new Hono() + .get( + '/user', + typiaValidator('query', typia.http.createValidateQuery<{ age_from?: User["age"], age_to?: User["age"] }>()), + (c) => { + const { age_from, age_to } = c.req.valid('query'); + return c.json({ age_from, age_to }); + } + ).put( + '/user/:id', + typiaValidator('param', typia.createValidate<{ id: `${number}` }>()), + typiaValidator('body', typia.createValidate()), + (c) => { + const { id } = c.req.valid('param'); + const user = c.req.valid('body'); + if (id !== user.id) { + return c.status(400).json({ message: 'id does not match' }); + } + return c.json({ id, user }); + } + ) + +export type AppType = typeof app; +export default app; +``` + +### Swagger UI での表示 + +生成した OpenAPI ドキュメントは [@hono/swagger-ui](https://hono.dev/examples/swagger-ui) で表示することができます。ここでメソッドチェーンで書かないことによってスキーマに出力せずに swagger UI のエンドポイントを追加できます。 + +if文で環境変数を見ているのは開発環境でのみ swagger UI を表示するためです。さらに、識別子置換と Dead Code Elimination をバンドラーで行うことで本番環境に一切依存するコードがない完全なゼロランタイムが実現できます。 + +```typescript +import { Hono } from 'hono'; +import { typiaValidator } from '@honojs/typia-validator/http'; +import typia, { type tags } from 'typia'; + +interface User { + id: number & tags.Type<'uint32'>; + name: string & tags.MaxLength<255>; + age: number & tags.Type<'uint32'> & tags.Maximum<150>; +} + +const app = new Hono() + // エンドポイントを定義 + +if (process.env.NODE_ENV !== "production") { + const openapi = await import('node:fs/promises') + .then((fs) => fs.readFile('openapi.json', 'utf-8')) + .then(JSON.parse); + const { swaggerUI } = await import('@hono/swagger-ui'); + + app.get('/docs/openapi.json', (c) => c.json(openapi)); + app.get('/docs', swaggerUI(openapi)); +} + +export type AppType = typeof app; +export default app; +``` + +## 今後の予定 + +今後は次のような機能を追加する予定です。 + +- Typia の JSON シリアライザを簡単に扱えるようにするヘルパーの作成 +- Return Type を簡単に指定できるヘルパーの作成 +- エラー表示をわかりやすくする +- Description の自動生成 +- タグの指定 + +## まとめ + +Hono + Typia で OpenAPI ドキュメントを生成するライブラリを作成しました。これにより、型から完全なゼロランタイムで OpenAPI ドキュメントを生成することができます。 + + +[^1]: Typia 調べ + https://typia.io/docs/validators/is/#performance +[^2]: Vite, Rollup, Webpack, esbuild, Rspack, Rolldown, Farm diff --git a/src/content/tags/hono.json b/src/content/tags/hono.json new file mode 100644 index 0000000..346dffe --- /dev/null +++ b/src/content/tags/hono.json @@ -0,0 +1,15 @@ +{ + "name": "Hono", + "description": "Hono は、 TypeScript および JavaScript のためのオープンソースな Web フレームワークである。 Web 標準に従っているという特徴がある。", + "image": "./hono.svg", + "links": [ + { + "text": "Hono 公式サイト", + "url": "https://hono.dev/" + }, + { + "text": "Hono - GitHub", + "url": "https://github.com/honojs/hono" + } + ] +} diff --git a/src/content/tags/hono.svg b/src/content/tags/hono.svg new file mode 100644 index 0000000..f6c4689 --- /dev/null +++ b/src/content/tags/hono.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/content/tags/openapi.json b/src/content/tags/openapi.json new file mode 100644 index 0000000..c634170 --- /dev/null +++ b/src/content/tags/openapi.json @@ -0,0 +1,14 @@ +{ + "name": "Open API", + "description": "OpenAPIは、RESTful APIを設計・記述するための標準仕様です。APIの構造や動作を記述することで、開発者間のコミュニケーションを効率化し、API設計・実装・テストをスムーズに進めることができます。", + "links": [ + { + "text": "OpenAPI Specification v3.0.4", + "url": "https://spec.openapis.org/oas/v3.0.4" + }, + { + "text": "OpenAPI Specification v3.1.1", + "url": "https://spec.openapis.org/oas/v3.1.1" + } + ] +} diff --git a/src/content/tags/typia.json b/src/content/tags/typia.json new file mode 100644 index 0000000..92e03a2 --- /dev/null +++ b/src/content/tags/typia.json @@ -0,0 +1,15 @@ +{ + "name": "Typia", + "description": "Typia は TypeScript の型からランタイムの関数を生成するライブラリです。型から高速なバリデータやシリアライザを作成できます", + "image": "./typia.png", + "links": [ + { + "text": "Typia 公式サイト", + "url": "https://typia.io/" + }, + { + "text": "Typia - GitHub", + "url": "https://github.com/samchon/typia" + } + ] +} diff --git a/src/content/tags/typia.png b/src/content/tags/typia.png new file mode 100644 index 0000000000000000000000000000000000000000..5835e979fc1f6ab16a6836d0483390d6d94ad5bd GIT binary patch literal 8344 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 literal 0 HcmV?d00001 From 2a8ed2edb3ec559105adca7290b3e725b80309b0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 14:30:45 +0000 Subject: [PATCH 2/2] [Bot] Update Blog Meta --- src/content/blog-metas/2024-12-15-hono-typia-openapi.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/content/blog-metas/2024-12-15-hono-typia-openapi.json diff --git a/src/content/blog-metas/2024-12-15-hono-typia-openapi.json b/src/content/blog-metas/2024-12-15-hono-typia-openapi.json new file mode 100644 index 0000000..0c08893 --- /dev/null +++ b/src/content/blog-metas/2024-12-15-hono-typia-openapi.json @@ -0,0 +1,3 @@ +{ + "postDate": "2024-12-15T14:30:40.899Z" +}