13
10
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

htmx & Hono & Bun & Pico CSSで爆速TODOアプリを作ってみた

Last updated at Posted at 2024-07-08

はじめに

htmx以外は全て初めて触る技術だったので、入門ということでTODOアプリを作ってみました。

Demo

todo (1).gif

使用した技術スタック

htmx

htmxに関しては、以前書いた記事に詳しく書いております。

もうjsなんていらない!世界で流行っているHTMXについてまとめてみた #JavaScript - Qiita

Hono

あらゆるランタイム上で動作する軽くて速いWebフレームワーク。

Bun

Zigで書かれた高速なJavaScriptランタイム。

Pico CSS

最小限のものだけが入ったシンプルなCSSフレームワーク

動作方法

以下からクローンしてください。(成果物のリポジトリ)

git clone https://github.com/tomo1227/todo-bun-hono-htmx-pico.git

Dev-Containerが使える方はコンテナを立ち上げて、bun run devすれ

bunをインストールしていない人は https://bun.sh/docs/installation からインストールして、以下のコマンドを叩いてください。

bun i
bun run dev

すると,http://localhost:3000/にアクセスできるようになります。

実装の紹介

今回は、サンプルアプリなので、Pico CSSやhtmxはCDNで導入しています。本番環境などでは、非推奨なので気をつけてください。

index.tsxは以下のように、割とシンプルに書くことがで来ました。
パッとみてFastAPIと似てる気がしました。

index.tsx
import { Hono } from "hono";
import "typed-htmx";
import TodoApp from "./components/TodoApp";
import Task from "./components/Task";
import Todo from "./components/Todo";

const app = new Hono();

let todos: Todo[] = [];

app.get("/", (c) => {
  return c.html(<TodoApp />);
});

app.post("/todo", async (c) => {
  const formData = await c.req.parseBody();
  const newId =
    todos.length > 0 ? Math.max(...todos.map((todo) => todo.id)) + 1 : 1;
  const newTodo = {
    id: newId,
    text: formData.todo as string,
  };
  todos.push(newTodo);

  return c.html(<Task todos={todos} />);
});

app.delete("/delete/:id", (c) => {
  const id: number = parseInt(c.req.param().id, 10);
  todos = todos.filter((todo) => todo.id !== id);
  return c.html(<Task todos={todos} />);
});

export default app;

htmxはformのところで多く使用した。できるだけjavascriptを書かずに、HTMLの部分だけで行うことを目指しました。

components/TaskTodo.jsx
  <main class="container">
    <h1 class="pico-color-pink-500">Welcome!</h1>
    <h1>Hono & Bun & htmx & Pico CSS Todo App</h1>
    <form
      hx-post="/todo"
      hx-target="#todos"
      {...{ "hx-on::after-request": "this.reset()" }}
    >
      <input
        id="todo"
        name="todo"
        type="text"
        required="true"
        placeholder="ここにタスクを入力"
      />
      <button type="submit">ADD TASK</button>
    </form>
    <section id="todos" hx-get="/todo" hx-trigger="load"></section>
  </main>

また、タスクを追加すると、以下のようなコンポーネントが作成される。
deleteボタンを押すと、hx-deleteが発火する。対象をsectionで、swap方法を指定すると、削除が簡単にようになった。

components/Task.tsx
import Todo from "./Todo";

interface TaskProps {
  todos: Todo[];
}

const Task = ({ todos }: TaskProps) => {
  return (
    <article class="container">
      {todos.map((todo) => (
        <section class="grid">
          <div id={todo.id.toString()} class="card">
            <strong>{todo.text}</strong>
          </div>
          <div></div>
          <button
            type="reset"
            class="pico-background-red-450"
            hx-delete={`/delete/${todo.id}`}
            hx-target="closest article"
            hx-swap="outerHTML"
          >
            DELETE
          </button>
        </section>
      ))}
    </article>
  );
};
export default Task;

ハマった箇所

htmxのhx-onでシンタックスエラー

フォーム送信後に入力をリセットするのだが、これをhtmxで書くと以下のようになる。

<form hx-on::after-request="this.reset()" />

しかし、HonoのJSXでは、上記の書き方はシンタックスエラーとなる。
ちなみに、コロンが1つだとエラーにならないが、動作しない。

<form hx-on="htmx:after-request: this.reset()" />

最終的に以下のようにすると動作することがわかった。

<form {...{ 'hx-on::after-request': 'this.reset()' }} />

成果物

今回作ったTodoアプリのリポジトリです。

参考文献

  1. https://github.com/Desdaemon/typed-htmx
  2. https://hono.dev/docs/getting-started/basic
  3. https://picocss.com/docs
  4. https://github.com/Pratap22/bun-todo-htmx
  5. Hono + htmx + Cloudflareは新しいスタック
13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10