htmx + mustache + oakで簡単なAPI描画

- html内の任意の属性を指定することで、jsの記述無しでインタラクティブなサイトを作成できる[htmx](https://htmx.org/)という技術を試しています。
- あらゆるイベントをトリガーにしたAjax通信を短い記法で簡単に実現することができます。
- 今回はJSONを返すAPIをhtmxで扱い、テンプレートエンジンのmustacheで描画する方法を記録いたします。

## 環境
- macOS 13.0
- deno 1.34.2
    - **未インストールの場合は[こちら](https://deno.com/manual@v1.33.4/getting_started/installation)**

## 手順

### APIの作成
- 対象のAPIを簡単に作成するため、denoの[oak](https://deno.land/x/oak@v12.5.0)を利用します。
- まず任意のフォルダにtsファイルを作成します。

```shell
mkdir ~/htmx-oak
cd ~/htmx-oak
touch app.ts
```

- 作成したtypescriptファイルを以下の内容にします。

```typescript
import { Application, Router } from "https://deno.land/x/oak@v12.5.0/mod.ts";
import { getQuery } from "https://deno.land/x/oak@v12.5.0/helpers.ts";

const app = new Application();
const router = new Router();

app.use(router.routes());
app.use(router.allowedMethods());

router
  .get("/", async (ctx) => {
    ctx.request.url.pathname = "index.html";
    await ctx.send({
      root: Deno.cwd(),
    });
  })
  .get("/api/users", (ctx) => {
    const users = [
      {
        id: 1,
        name: "tanaka",
      },
      {
        id: 2,
        name: "suzuki",
      },
      {
        id: 3,
        name: "sasaki",
      },
      {
        id: 4,
        name: "takahashi",
      },
    ];

    // クエリパラメータ取得
    const params = getQuery(ctx);
    const search = params.search;

    // なければそのまま返す。
    if (!search) {
      ctx.response.body = users;
      return;
    }

    // クエリパラメータの値でフィルタリングして返す。
    ctx.response.body = users.filter((user) => {
      return user.name.includes(search);
    });
  });

await app.listen({ port: 8000 });
```

- 記述後、以下のコマンドを実行してアプリサーバーを起動してください。

```shell
deno run --allow-net --allow-read app.ts
```

- `localhost:8000/api/users`にアクセスして、以下のレスポンスになっていて、簡易的なUser APIが作成できていることを確認します。

```json
[
  {
    "id": 1,
    "name": "tanaka",
  },
  {
    "id": 2,
    "name": "suzuki",
  },
  {
    "id": 3,
    "name": "sasaki",
  },
  {
    "id": 4,
    "name": "takahashi",
  },
]
```

### htmxの用意
- APIが用意できたので、そのレスポンスを描画するためのHTMLファイルを以下で作成します。

```shell
touch index.html
```

- 作成したhtmlの内容を以下にします。

```html
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>try-htmx</title>
  <script src="https://unpkg.com/htmx.org"></script>
  <script src="https://unpkg.com/htmx.org/dist/ext/client-side-templates.js"></script>
  <script src="https://unpkg.com/mustache@latest"></script>
</head>

<body>
  <div hx-ext="client-side-templates">
    <input type="search" name="search" hx-get="/api/users" hx-swap="innerHTML" hx-target="#content"
      mustache-template="users" hx-trigger="load, keyup changed delay:500ms, search">
    <div id="content"></div>
    <template id="users">
      <ul>
        {{#.}}
        <li>{{id}} : {{name}}</li>
        {{/.}}
      </ul>
    </template>
  </div>
</body>

</html>
```

- 上記の記述では以下のscriptをheadタグで読み込んでいます。
    - htmxの読み込み : [CDN(unpkg)](https://unpkg.com/htmx.org)
    - htmxのテンプレートエンジンサポート : [CDN(unpkg)](https://unpkg.com/htmx.org@1.9.2/dist/ext/client-side-templates.js)
    - mustacheの読み込み : [CDN(unpkg)](https://unpkg.com/mustache@latest)
- また**属性とそれに対する動作**としては以下です。
    - **hx-get** : `/api/users`にGETリクエスト。
    - **hx-trigger** : 「**読み込み・入力後の500ms後**」に**hx-get**を発火。
    - **hx-swap** : レスポンスの描画。
    - **hx-target** : 描画先の要素。

- 再度`deno run --allow-net app.ts`を実行して、上記の動作の流れになっていることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336499/6040bd2a-dbea-8f87-8b9e-ace40cf52d49.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336499/075c4ce0-53ac-06cc-e1ca-aeedb47a5f7a.png)

- 以上です。

## まとめ
- 簡易的な試用であったが、htmxを利用すればあらゆるイベントをトリガーにしたajax通信が簡単に実現可能。
- 簡易的なものならhtmlファイルだけでまとまり、また動作の流れも上から追えるのも非常に便利。
- より便利な機能もあるらしいので、継続的に調査する。

## 参考
- https://htmx.org/
- https://htmx.org/extensions/client-side-templates/
- https://deno.land/x/oak@v12.5.0
- https://mustache.github.io/mustache.5.html