Go, Templ, HTMX: JSフレームワーク不要で型安全なフルスタックWebアプリを構築する

Development tutorial - IT technology blog
Development tutorial - IT technology blog

フロントエンドとバックエンド間の「型の不一致」という苦しみ

こんなシナリオを想像してみてください。データベースのフィールドを user_id (string) から ID (int) に変更したとします。バックエンドのGoは更新しましたが、フロントエンドのReactを更新し忘れてしまいました。その結果、TypeScriptがAPIからの変更をキャッチできず、本番環境で実行時エラーが発生してしまいます。

プロジェクトの両端で2つの型定義を維持するのは、実に大きな負担です。管理画面(Admin Dashboard)や基本的なCRUDフォームにおいて、数MBものJavaScriptライブラリを読み込み、CORSを処理し、複雑な状態管理(State Management)を行うのは、リソースの無駄になることが多いです。そこで、Go、Templ、HTMXのトリオが代替案として登場します。すべてのロジックをサーバー側に戻しつつ、シングルページアプリケーション(SPA)のようなスムーズな体験を維持します。

なぜこの組み合わせが効果的なのか?

この組み合わせは、パフォーマンスとコードの安全性という課題を直接解決します。

  • Go: 高いパフォーマンスと優れた並行処理(concurrency)能力でロジック処理を担当します。
  • HTMX (~14KB): ReactやVueを使う代わりに、HTMXを使用することでHTML属性を通じて直接AJAXやWebSocketsを実行できます。JavaScriptを一行も書かずに、ページの一部を更新することが可能です。
  • Templ: これが最も重要な要素です。Templを使用すると、Goに近い構文でHTMLを記述し、それを純粋なGoコードにコンパイルできます。コンポーネントに誤った型のデータを渡すと、コンパイラが即座にエラーを報告します。

実践:型安全なTodoリストアプリの構築

ビジネスロジックからユーザーインターフェースまで、データが安全に流れる様子を確認するために、小さなアプリケーションを作成してみましょう。

1. 環境構築

まず、プロジェクトを初期化し、必要なツールをインストールします。

mkdir go-htmx-demo && cd go-htmx-demo
go mod init go-htmx-demo

# コード生成用のTempl CLIをインストール
go install github.com/a-h/templ/cmd/templ@latest

# ルーティング処理のためにEcho Frameworkを追加
go get github.com/labstack/echo/v4
go get github.com/a-h/templ

2. Templによるコンポーネント定義

components.templ ファイルを作成します。ここでの特徴は、HTML内でGoの構造体(struct)を直接使用できる点です。

package main

type Todo struct {
    ID   int
    Task string
}

templ Page(todos []Todo) {
    <!DOCTYPE html>
    <html>
    <head>
        <title>Go + Templ + HTMX</title>
        <script src="https://unpkg.com/[email protected]"></script>
        <script src="https://cdn.tailwindcss.com"></script>
    </head>
    <body class="bg-slate-50 p-10">
        <div class="max-w-md mx-auto bg-white p-6 rounded-lg shadow-sm">
            <h1 class="text-xl font-bold mb-4">マイタスク</h1>
            
            <form hx-post="/add" hx-target="#todo-list" hx-swap="beforeend" class="flex gap-2 mb-4">
                <input type="text" name="task" class="border rounded px-2 py-1 flex-1" />
                <button type="submit" class="bg-indigo-600 text-white px-4 py-1 rounded">追加</button>
            </form>

            <ul id="todo-list" class="divide-y">
                for _, todo := range todos {
                    @TodoItem(todo)
                }
            </ul>
        </div>
    </body>
    </html>
}

templ TodoItem(todo Todo) {
    <li class="py-2 text-gray-700">{ todo.Task }</li>
}

属性 hx-post="/add" は、フォーム送信時にサーバーへリクエストを送り、返ってきた結果を #todo-list の最後に追加するようHTMXに指示します。

3. バックエンドの処理

main.go ファイルで、データロジックと定義したインターフェースを接続します。

func main() {
    e := echo.New()
    todos := []Todo{{ID: 1, Task: "Goを学ぶ"}}

    e.GET("/", func(c echo.Context) error {
        return Page(todos).Render(c.Request().Context(), c.Response().Writer)
    })

    e.POST("/add", func(c echo.Context) error {
        newTodo := Todo{ID: len(todos) + 1, Task: c.FormValue("task")}
        todos = append(todos, newTodo)
        // ページ全体をリロードせず、TodoItemコンポーネントのみを返す
        return TodoItem(newTodo).Render(c.Request().Context(), c.Response().Writer)
    })

    e.Logger.Fatal(e.Start(":8080"))
}

アプリを実行するには、まず templ generate を実行してテンプレートファイルを標準のGoコードに変換する必要があります。

実践的なアドバイス:いつこのスタックを選ぶべきか?

開発プロセスにおいて、データベースや外部APIからの生データの処理は煩雑になりがちです。私はよく toolcraft.appJSON Formatter を使用して、Goの構造体にマッピングする前にデータ構造を素早く確認しています。これにより、モデリングの段階でのロジックエラーを最小限に抑えることができます。

このアプローチの最大の利点は、Single Source of Truth(信頼できる唯一の情報源) です。Todo 構造体のフィールドを変更すると、そのテンプレートを使用しているすべての場所でコンパイラがエラーを出してくれます。通常のRESTやGraphQLのように、フロントエンドとバックエンドの間でデータが「食い違う」心配をする必要はありません。

おわりに

Go、Templ、HTMXは、単なる古いSSR技術への後退ではありません。これは開発プロセスを簡素化するモダンなアプローチです。複雑な何千ものJavaScriptファイルを管理することなく、型安全なシステムの安心感、Goのパフォーマンス、そしてHTMXによるスムーズな体験を手に入れることができます。開発スピードと安定性を優先するプロジェクトであれば、ぜひこのスタックを試してみてください。

Share: