プログラムを勉強するときは、先達の方々のコードを読むと学びが得られることが多々あります。
ここのところ Bun と Elysia を集中的に勉強してるのですが、これもやはり他の方のコードを読むことも必要だな、ということで、今回は「ElysiaJS と HTMX を使用した簡単なログインデモ (htmx-bun-login) 」というのがログイン用のサンプルとしてシンプルで判りやすいので試してみました。
Github はここにあります。
cindersax/ htmx-bun-login
今回の成果物 (たぶん期間限定公開)
先に今回のログインサンプルの成果物を上げておきます。まぁ、そのうち止めると思いますけど参考までに。ブラウザで開き user user でログインを試せます。
今回の環境
クラウド: 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 アプリケーション開発の基礎を理解できるように、シンプルさと明瞭さに重点を置いています。
プロジェクトの構造
・src/app.tsx: サーバーのセットアップ、ルート定義、および認証の処理。
・src/layouts.tsx: アプリケーション全体で使用される JSX ベースのレイアウト テンプレートが含まれています。
使用されている技術
・HTMX: HTML の拡張機能。HTML で高度な機能を直接有効にします。
・JSX: UI コンポーネントをより読みやすい形式で記述するためのテンプレート エンジンとして使用されます。
・Bun: パッケージ マネージャーとバンドラーが組み込まれた高速なオールインワン JavaScript ランタイム。
・Elysia.js: 効率的でスケーラブルな Node.js サーバー側アプリケーションを構築するための JavaScript フレームワーク。
セットアップとインストール
では入れてみます。
Bun と Elysia はインストール済みとします。もしわからなければ、昨日書いたこの記事にあるので参考にしてみてください。
リポジトリからクローンを作成します
$ git clone https://github.com/cindersax/htmx-bun-login
依存関係(必要なパッケージ)をインストールします
$ bun install
サーバーの起動
# 開発の場合:
$ bun install
# 本番環境の場合:
$ bun run start
これで、package.json にあるこれらが起動するわけです。
"scripts": {
"start": "bun run app.tsx",
"dev": "bun --hot run src/app.tsx"
},
生成されたディレクトリとファイル
htmx-bun-login/
├─ README.md
├─ bun.lockb
├─ node_modules/
├─ package.json
├─ src/
│ ├─ app.tsx
│ └─ layouts.tsx
└─ tsconfig.json
コードを理解する
src/app.tsx
・メインサーバーのセットアップとエントリポイント。
・ルートを定義し、HTTP リクエストを処理します。
・JWT トークンと Cookie に@elysiajs/jwtを使用して認証を実装します。
src/layouts.jsx (※サンプルはlayouts.tsxになってるけど多分問題ない。)
・さまざまなページ間で一貫した外観と操作性を実現するために、JSX ベースのレイアウト テンプレートを定義します。
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 の中身も見ておきましょう。
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選