honoはじめ

## はじめに
シゴトでdevcontainerを使ってNextJSを動かすととても重くて、最近はフロントをhostで開発している。そもそもdevcontainerで快適なぐらいのないの？ってのでたどり着いたフレームワーク。FastAPIのようなノリで使えそう。まぁでもこの記事ではdockerは使わない。あくまでhonoの記事。

このIssueで使うかー

https://github.com/duri0214/portfolio/issues/59

## プロジェクトの作成
日本製！

https://hono.dev/docs/

```console:console
cd C:\Users\yoshi\OneDrive\dev
npm create hono@latest
  Need to install the following packages:
    create-hono@0.14.3
  Ok to proceed? (y) y
  create-hono version 0.14.3
  ? Target directory hono_playground
  ? Which template do you want to use? nodejs
  ? Do you want to install project dependencies? yes
  ? Which package manager do you want to use? npm
  ✔ Cloning the template
  ✔ Installing project dependencies
  🎉 Copied project files
  Get started with: cd hono_playground
  npm notice 
  npm notice New major version of npm available! 9.6.7 -> 11.0.0
  npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.0.0
  npm notice Run npm install -g npm@11.0.0 to update!
  npm notice

code .\hono_playground\
npm run dev

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/49562dca-6031-83b7-5b23-9f8c947c7ca8.png)

## tsconfig
- なんだかhonoの標準のgenerateが古いらしい... importの仕方とかが慣れないかたちになるのでtsconfigをいじる
```diff_json
{
  "compilerOptions": {
    "target": "ESNext",
-   "module": "NodeNext",
+   "module": "ESNext",
+   "moduleResolution": "bundler",
    "strict": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true,
    "types": [
      "node"
    ],
    "jsx": "react-jsx",
    "jsxImportSource": "hono/jsx",
  }
}
```

## テキストフォームを使ってPOST送信
- json でPOSTする
- `c.req.json()` で受ける
- `json` で返す
- `result` を受けてHTMLを書き換える

:::note info
ついでにこれのエッセンスもいれちゃおう
[HTMLのinputの配列は添字に key が指定できる](https://qiita.com/YoshitakaOkada/items/62ded657fa147bd0316a)

```json
{
  "use[1]": "on",
  "name_short[1]": "資格A",
  "name_long[1]": "資格A 長い名前",
  "address[1]": "住所A",
  "use[2]": "on",
  "name_short[2]": "資格B",
  "name_long[2]": "資格B 長い名前",
  "address[2]": "住所B"
}
```
:::

```ts:src\index.ts
import { serve } from "@hono/node-server";
import { Hono } from "hono";

const app = new Hono();

app.get("/", (c) => {
  return c.html(`
    <html>
      <head>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
      </head>
      <body>
        <div class="container mt-5">
          <h1>フォーム送信デモ</h1>
          <form id="demoForm" class="mt-4">
            <div class="mb-3">
              <label for="name" class="form-label">名前</label>
              <input type="text" class="form-control" id="name" name="name" required>
            </div>
            <div class="mb-3">
              <label for="message" class="form-label">メッセージ</label>
              <textarea class="form-control" id="message" name="message" rows="3" required></textarea>
            </div>

            <h3 class="mt-4">資格リスト</h3>
            <table class="table">
              <thead>
                <tr>
                  <th>使う</th>
                  <th>資格名</th>
                  <th>資格名（長い名前）</th>
                  <th>住所</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td><input type="checkbox" name="use[1]" checked></td>
                  <td><input type="text" name="name_short[1]" value="資格A" class="form-control"></td>
                  <td><input type="text" name="name_long[1]" value="資格A 長い名前" class="form-control"></td>
                  <td><input type="text" name="address[1]" value="住所A" class="form-control"></td>
                </tr>
                <tr>
                  <td><input type="checkbox" name="use[2]"></td>
                  <td><input type="text" name="name_short[2]" value="資格B" class="form-control"></td>
                  <td><input type="text" name="name_long[2]" value="資格B 長い名前" class="form-control"></td>
                  <td><input type="text" name="address[2]" value="住所B" class="form-control"></td>
                </tr>
              </tbody>
            </table>

            <button type="submit" class="btn btn-primary">送信</button>
          </form>
          <div id="responseMessage" class="mt-3"></div>
        </div>
        <script>
          const form = document.getElementById('demoForm')
          const responseMessage = document.getElementById('responseMessage')

          form.addEventListener('submit', async (event) => {
            event.preventDefault()
            const formData = new FormData(form)
            const data = Object.fromEntries(formData)
            const response = await fetch('/submit', {
              method: 'POST',
              headers: {'Content-Type': 'application/json'},
              body: JSON.stringify(data)
            })

            const result = await response.json()
            
            responseMessage.innerHTML = \`<div class="alert alert-success">サーバーからの応答: \${result.message}<br>\${JSON.stringify(result.certifications, null, 2)}</div>\`
          })
        </script>
      </body>
    </html>
  `);
});

app.post("/submit", async (c) => {
  const body = await c.req.json();
  console.log(body);

  const { name, message } = body;

  // 配列データを再構築
  const certifications: Array<{
    id: string;
    name_short: string;
    name_long: string;
    address: string;
    used: boolean;
  }> = [];

  for (const key in body) {
    if (key.startsWith("use[")) {
      const id = key.match(/\[(\d+)\]/)?.[1]; // `use[1]` -> `1`
      if (id) {
        certifications.push({
          id,
          name_short: body[`name_short[${id}]`] || "", // 対応する name_short を取得
          name_long: body[`name_long[${id}]`] || "", // 対応する name_long を取得
          address: body[`address[${id}]`] || "", // 対応する address を取得
          used: body[key] === "on", // チェックボックスが選択されている場合
        });
      }
    }
  }

  return c.json({
    message: `こんにちは、${name}！メッセージ「${message}」を受け取りました。`,
    certifications,
  });
});

const port = 3000;
console.log(`Server is running on http://localhost:${port}`);

serve({
  fetch: app.fetch,
  port,
});

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/77305764-e84d-57de-8fff-3391e52ec496.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94562/850babff-412b-4a6a-1b09-007e31bbc404.png)