【Hono】作成したAPIをSwagger UI上で動作確認したい

# はじめに
最近風邪を引いて3キロの減量に成功した、ノベルワークスのりょうちん([ryotech34](https://x.com/ryotech34))です。
今回はHonoを勉強した時に実装したものを軽くご紹介したいと思います。

# Honoとは
Honoとは、TypeScriptやJavaScript向けのオープンソースWebフレームワークとして最近とてもホット🔥なフレームワークです。
CloudflareやAWS Lambda、NodeやAzure Functionsなど様々なランタイム上で動作することが出来るのが特徴です。

https://hono-ja.pages.dev/docs/concepts/motivation

# モチベーション
今回は私がPythonのFastAPI村出身ということで、エンドポイントをSwagger UI上でテストしたいというモチベーションがありました。
また、ドキュメントとしてAPI設計書などを作りたくないため、開発がてらドキュメントも作れればという思いもありました。

# 前提
## 今回のコンセプト
今回は、ユーザーに関連したCRUD操作のエンドポイントを作成し、Swagger UIでまとめるというコンセプトです。

## インストール
Honoで用意されているzod-openapiを活用していきます。
付随して、zodも使用します。
```
npm i @hono/swagger-ui @hono/zod-openapi @hono/node-server" zod
```

## フォルダ構成
ユーザーに関連したエンドポイントを追加したいときは、user配下のrouters内に追加します。
組織やプロジェクトなど、ユーザー以外のドメインに関するエンドポイントを追加したい時は、controller配下にフォルダを作成しuser同様の構成で作成するイメージです。
```md
hono-sample/
├── src/
│   └── controller/
│       ├── user/
│       │   ├── routers/
│       │   │   └── create.ts
│       │   └── routes.ts
│       ├── app.ts
│       └── server.ts
├── package.json
└── package-lock.json
```


# 実装
## エンドポイント定義
`create.ts`にリクエストボディやレスポンス形式、ハンドラーを定義していきます。
```ts: create.ts
import { z } from "zod";
import { createRoute, RouteHandler } from "@hono/zod-openapi";

const createUserBodySchema = z.object({
  name: z.string(),
  email: z.string().email()
});

const createUserRoute = createRoute({
    path: "/create",
    method: "post",
    request: {
        body: {
            content: {
                "application/json": {
                    schema: createUserBodySchema,
                },
            },
        },
    },
    responses: {
        200: {
            description: "OK",
            content: {
                "application/json": {
                    schema: z.object({
                        id: z.string(),
                        name: z.string(),
                        email: z.string().email(),
                    }),
                },
            },
        },
        400: {
            description: "BAD REQUEST",
            content: {
                "application/json": {
                    schema: z.object({
                        message: z.string(),
                    }),
                },
            },
        },
    },
    tags: ["users"],
});

const createUserHandler: RouteHandler<typeof createUserRoute> = async (c) => {
    try {   
        const user = {
            id: "asdf",
            name: "John Doe",
            email: "john.doe@example.com"
        }
        return c.json(user, 200);
    } catch (error) {
        return c.json({ message: "User creation failed" }, 400);
    }
};

export { createUserRoute, createUserHandler };
```

## ユーザー関連のルーティングとしてまとめる
今回はcreateだけですが、他のCRUD操作や様々なエンドポイントが作られることが予想されます。今回はUser関連の情報としてまとめてあげます。
```ts: route.ts
import { OpenAPIHono } from "@hono/zod-openapi";
import { createUserRoute, createUserHandler } from "./routers/create";

const userRoutes = new OpenAPIHono();

userRoutes.openapi(createUserRoute, createUserHandler);

export default userRoutes;
```

## 全てのルーティングをまとめる
User以外にもOrganizationやProjectなど様々なルーティング情報をappでまとめています。

```ts: app.ts
import { OpenAPIHono } from '@hono/zod-openapi';
import { swaggerUI } from '@hono/swagger-ui';
import userRoutes from './user/routes';

const app = new OpenAPIHono();

app.route('/users', userRoutes);

const openAPIDoc = {
  openapi: "3.0.0",
  info: {
    title: "User API",
    version: "1.0.0",
    description: "User API",
  },
  servers: [
    {
      url: "http://localhost:8080",
      description: "Local Server",
    },
  ],
  tags: [
    {
      name: "users",
      description: "User API",
    },
  ],
};

app.doc('/swagger.json', openAPIDoc);
app.get('/docs', swaggerUI({ url: '/swagger.json' }));

export default app;
```

## Swagger UIで確認してみる
`localhost:8080/docs`で確認してみるときちんと表示されていることが確認できました。

![](https://storage.googleapis.com/zenn-user-upload/97bb62450567-20250319.png)

また、きちんと定義してレスポンス形式まで表示されていることが確認できます。

![](https://storage.googleapis.com/zenn-user-upload/55c0a370dac5-20250319.png)

# おわりに
今回は、Honoを使用して作成したAPIをSwagger UI上で動作確認したいというモチベーションで実装してみました。Python出身の僕でもすんなり実装できてとても使いやすいフレームワークだと実感しています。
ミドルウェアなども実装できるため、ロギングや認証などカスタマイズ性も他のフレームワークに劣らないと感じています。また、最近は勉強会もよく行われており学ぶにうってつけのタイミングだと思います！
これからHonoをもっと使っていきたいと思います。

# 参考
- [Hono](https://hono-ja.pages.dev/docs/getting-started/basic)
- [HonoでAPIのドキュメントを自動生成する](https://zenn.dev/praha/articles/d1d6462a27e37e)