【Elysia/Bun】ElysiaJS と HTMX を使用した簡単なログインデモ (htmx-bun-login) を試した

プログラムを勉強するときは、先達の方々のコードを読むと学びが得られることが多々あります。

ここのところ Bun と Elysia を集中的に勉強してるのですが、これもやはり他の方のコードを読むことも必要だな、ということで、今回は[「ElysiaJS と HTMX を使用した簡単なログインデモ (htmx-bun-login) 」](https://github.com/cindersax/htmx-bun-login)というのがログイン用のサンプルとしてシンプルで判りやすいので試してみました。

Github はここにあります。

cindersax/ htmx-bun-login

https://github.com/cindersax/htmx-bun-login

# 今回の成果物 (たぶん期間限定公開)　

先に今回のログインサンプルの成果物を上げておきます。まぁ、そのうち止めると思いますけど参考までに。ブラウザで開き user user でログインを試せます。

<a href=http://74.226.208.203:9004/ target=bun> http://74.226.208.203:9004/<br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7103/5593b911-5593-91a0-8871-09a5344a4088.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7103/fff2078e-d7a0-4e7e-c93d-b1e0bf4d3ae5.png)
</a>

# 今回の環境
クラウド： Azure VM (これは何でも良い)
OS：　Ubuntu 20.04.6 LTS (GNU/Linux 5.15.0-1050-azure x86_64)
Bun：　v1.0.18
Elysia： v0.7.30

# 説明書き
一応、こんな説明が書いてあります

>HTMX-Bun ログイン プロジェクトへようこそ!このプロジェクトでは、HTMX、JSX (テンプレート エンジンとして)、Bun、および Elysia.js で構成される最新の Web 開発スタックを使用してログイン システムを構築する方法を示します。初心者がこれらのテクノロジを使用した Web アプリケーション開発の基礎を理解できるように、シンプルさと明瞭さに重点を置いています。

>プロジェクトの構造
<b>・src/app.tsx:</b> サーバーのセットアップ、ルート定義、および認証の処理。
<b>・src/layouts.tsx:</b> アプリケーション全体で使用される JSX ベースのレイアウト テンプレートが含まれています。

>使用されている技術
<b>・HTMX:</b> HTML の拡張機能。HTML で高度な機能を直接有効にします。
<b>・JSX:</b> UI コンポーネントをより読みやすい形式で記述するためのテンプレート エンジンとして使用されます。
<b>・Bun:</b> パッケージ マネージャーとバンドラーが組み込まれた高速なオールインワン JavaScript ランタイム。
<b>・Elysia.js:</b> 効率的でスケーラブルな Node.js サーバー側アプリケーションを構築するための JavaScript フレームワーク。

# セットアップとインストール
では入れてみます。

Bun と Elysia はインストール済みとします。もしわからなければ、[昨日書いたこの記事](https://qiita.com/toshirot/items/2f0db79b3c2480e64f41)にあるので参考にしてみてください。

#### リポジトリからクローンを作成します
```sh:リポジトリからクローンを作成します
$ git clone https://github.com/cindersax/htmx-bun-login
```

#### 依存関係(必要なパッケージ)をインストールします
```sh:依存関係のインストール
$ bun install
```
#### サーバーの起動
```sh:サーバーの起動
# 開発の場合: 
$ bun install

# 本番環境の場合: 
$ bun run start
```
これで、package.json にあるこれらが起動するわけです。
```json: package.json の "scripts"
  "scripts": {
    "start": "bun run app.tsx",
    "dev": "bun --hot run  src/app.tsx"
  },
```
# 生成されたディレクトリとファイル
```sh: 生成されたディレクトリとファイル

htmx-bun-login/
├─  README.md
├─  bun.lockb
├─  node_modules/
├─  package.json
├─  src/
│   ├─ app.tsx
│   └─ layouts.tsx
└─ tsconfig.json
```

>コードを理解する
<b>src/app.tsx</b>
・メインサーバーのセットアップとエントリポイント。
・ルートを定義し、HTTP リクエストを処理します。
・JWT トークンと Cookie に@elysiajs/jwtを使用して認証を実装します。
<b>src/layouts.jsx</b> (※サンプルはlayouts.tsxになってるけど多分問題ない。)
・さまざまなページ間で一貫した外観と操作性を実現するために、JSX ベースのレイアウト テンプレートを定義します。

# src/app.tsx の中身
```tsx: src/app.tsx の中身
import { html } from "@elysiajs/html";
import { jwt } from "@elysiajs/jwt";
import { Elysia, t } from "elysia";
import { Protected, NotLogged, Logged, Login } from "./layouts";

// this is sample users, you should use a database in production
const users = [
  {
    username: "admin",
    password: await Bun.password.hash("admin"),
  },
  {
    username: "user",
    password: await Bun.password.hash("user"),
  },
];

new Elysia()
  .use(
    jwt({
      name: "jwt",
      // This is a secret key, you should change it in production
      secret: "your secret",
    })
  )
  .use(html())
  .get("/", async ({ jwt, cookie: { auth } }) => {
    const authCookie = await jwt.verify(auth.value);
    return authCookie ? <Logged /> : <Login />;
  })
  .get("/protected", async ({ jwt, cookie: { auth } }) => {
    const authCookie = await jwt.verify(auth.value);
    return authCookie ? (
      <Protected username={authCookie.username} />
    ) : (
      <NotLogged />
    );
  })
  .post(
    "/login",
    async ({ jwt, body, set, cookie: { auth } }) => {
      const { password, username } = body;

      const user = users.find((user) => user.username === username);

      if (user && (await Bun.password.verify(password, user.password))) {
        const token = await jwt.sign({ username });

        auth.set({
          value: token,
          httpOnly: true,
        });

        set.headers = {
          "Hx-redirect": "/protected",
        };
        return "Login successful!";
      }
      return "Invalid credentials";
    },
    {
      body: t.Object({
        username: t.String(),
        password: t.String(),
      }),
    }
  )
  .get("/logout", ({ set, cookie: { auth } }) => {
    auth.remove();
    set.redirect = "/";
  })
  .listen(9004);
```

判りやすいルーティングになってますね。いろいろ勉強になるます(＜アーニャかっ)。パスワードはここでは直打ちしてハッシュ化してるけど、まぁ本番では、あらかじめ作ったハッシュ値やデータベースや.envなどに分けておくとかにしといた方が良いかな。でもまぁ、単純なサイトならこれでも良いけど。

# src/layouts.tsx の中身

src/layouts.tsx の中身も見ておきましょう。

```tsx: src/layouts.tsx の中身
export const BaseLayout = ({ children }: any) => {
  return (
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Demo login</title>
        <script src="https://unpkg.com/htmx.org@1.9.9"></script>
      </head>
      <body id="main">{children}</body>
    </html>
  );
};

export const Login = () => {
  return (
    <BaseLayout>
      <div>
        <h1>Login</h1>
        <p>Please login to continue. ※一応 user user でログインを試せます。</p>

        <form hx-post="/login" hx-target=".error" hx-swap="innerHTML">
          <fieldset>
            <legend>Login</legend>
            <p>
              <label for="username">Username:</label>
              <input type="text" id="username" name="username" />
            </p>
            <p>
              <label for="password">Password:</label>
              <input type="password" id="password" name="password" />
            </p>

            <button type="submit">Login</button>
            <p>
              <span class="error"></span>
            </p>
          </fieldset>
        </form>
      </div>
    </BaseLayout>
  );
};

export const Logged = () => {
  return (
    <BaseLayout>
      <h1>Login</h1>
      <p>You are already logged in</p>
      <a href="/logout">Logout</a>
    </BaseLayout>
  );
};

export const NotLogged = () => {
  return (
    <BaseLayout>
      <h1>Protected route</h1>
      <p>Hi, you are not logged in</p>
      <a href="/login">Login</a>
    </BaseLayout>
  );
};

export const Protected = ({ username }: { username: string }) => {
  return (
    <BaseLayout>
      <h1>Protected route</h1>
      <p>Hi {username} ログインできたやん。</p>
      <a href="/logout">Logout</a>
    </BaseLayout>
  );
};
```
jsx（JavaScript XML）は、JavaScriptの構文拡張で、React、Next.js、Vue などなどで使われていて、まぁ、最近の流行りですが、説明はここでは省略します。

以上、自分は何もせずに git clone するだけでログインテンプレートが手に入り、簡単に使えて勉強にもなる幸せな今回でした。


# 最近 Qiita に書いた Bun 関連の記事10選

https://qiita.com/advent-calendar/2023/bunjs

https://qiita.com/toshirot/items/2f0db79b3c2480e64f41

https://qiita.com/toshirot/items/94616478d3e73e7cc5b9

https://qiita.com/toshirot/items/f6bd561bc2522f1d26e0

https://qiita.com/toshirot/items/a5dc53ef191d20bbeb42

https://qiita.com/toshirot/items/74476af27e67d8534c5a

https://qiita.com/toshirot/items/94961b6dde72be80ab0c

https://qiita.com/toshirot/items/7a0555b46b3a64a4f643

https://qiita.com/toshirot/items/c4d090bc38f9b85d8cdd

https://qiita.com/toshirot/items/4e8e571dd50a45c651f0

https://qiita.com/toshirot/items/e4eb635596ae4f63bdfd