Go + Inertia.js v3
API を挟まず、Go のハンドラーから直接 React のページを。
tug は、フロントエンドに Inertia.js を使う Go アプリのための Web フレームワークです。ハンドラーは型付きの props で React、Vue、Svelte のページをレンダリングし、アプリ全体が単一のバイナリになります。
$ go install github.com/cuonggt/tug/cmd/tug@latest
$ tug new blog # -auth for accounts
$ cd blog
$ tug dev
tug │ serving http://localhost:8080, built in 268ms
^C
$ tug build
tug build: blog, 9.6 MB, frontend included
仕組み
Go の構造体が、ページの props にも、その型にもなる。
ハンドラーは構造体を渡してページをレンダリングし、ページのコンポーネントはその構造体を props として受け取ります。tug gen が構造体の TypeScript を書き出すので、コンポーネントは Go のコードに照らしてチェックされます。Go のコードが変わると、tug dev が書き出し直します。
type PostsIndexProps struct {
Posts []Post `json:"posts"`
Stats inertia.DeferProp[Stats] `json:"stats"`
}
var PostsIndex = tug.Page[PostsIndexProps]("Posts/Index")
func index(c *tug.Ctx) error {
return PostsIndex.Render(c, PostsIndexProps{
Posts: store.List(),
Stats: inertia.Defer(store.Count), // after the page shows
})
}// Code generated by tug gen from the Go types. DO NOT EDIT.
export interface PostsIndexProps {
posts: Post[]
stats?: Stats
}import { Deferred, Link } from '@inertiajs/react'
import type { PageProps } from '../../tug/pages'
import { route } from '../../tug/routes'
type Props = PageProps<'Posts/Index'>
export default function Index({ posts, stats }: Props) {
return (
<>
<Deferred data="stats" fallback={<p>Counting…</p>}>
{stats && <p>{stats.posts} posts</p>}
</Deferred>
{posts.map((post) => (
<Link
key={post.id}
href={route('posts.show', { id: post.id })}
>
{post.title}
</Link>
))}
</>
)
}機能
アプリに必要なものが、はじめから Inertia と連携済み。
セッション、フラッシュメッセージ、バリデーションエラー、CSRF は、設定なしで Inertia と連携して動きます。土台は Go の標準ライブラリです。
Inertia v3 をまるごと
遅延・マージ・once・オプショナルの各 props、パスを指定した部分リロード、無限スクロール、Go から設定するページのタイトルと説明。
Go から生成される型
tug genは、各ページの props、名前付きルート、各フォームが送信する内容、フラッシュデータのキーを TypeScript で書き出します。検証が組み込まれたフォーム
c.BindValidはリクエストをバインドし、構造体タグで検証します。検証に通らないフォームはエラーとともに戻され、各フィールドはフォーカスが外れた時点で検証されます。フラグ 1 つでアカウント機能
tug new -authで、ユーザー登録、メールアドレスの確認、二要素認証のコード、パスキー、API トークン、通知が揃います。データベースは SQLite、Postgres、MySQL から選べます。バックグラウンドジョブ
失敗すると再実行され、どのタイムゾーンでもスケジュールでき、何度投入されても 1 回だけ実行でき、アプリ自身のトランザクションの中で投入できるジョブ。
バイナリ 1 つ
tug buildは、フロントエンドを内蔵した静的バイナリを 1 つ作ります。フロントエンドはアプリ自身が gzip で圧縮して配信します。アプリには Dockerfile も付いています。必要ならサーバーでレンダリング
tug new -ssrなら、初回訪問のページを Inertia 自身の SSR でレンダリングします。レンダリングはアプリと並んで動く Node が行い、Node がなければページはブラウザーでレンダリングされます。ブラウザーなしでテスト
tugtestパッケージは、Go のテストで使う Inertia のクライアントです。訪問、フォーム、アップロード、ログインを扱い、ページの props はその Go の構造体に読み込みます。すべてのインスタンスにイベントを
broadcastパッケージは、チャンネル上のイベントをアプリのデータベース経由で、すべてのインスタンスで開かれているページに届けます。ページは変わったものを再読み込みします。
なぜ Go か
すべてのコアで動き、ミリ秒で起動する小さなバイナリ 1 つ。
Go なら、Web アプリは静的バイナリ 1 つになり、1 つのプロセスですべてのコアを使い、ミリ秒単位で起動し、数十 MB のメモリで動きます。tug は Inertia のアプリでも、そのすべてを保ちます。フロントエンドのビルドを内蔵した静的バイナリ 1 つを、Go 標準の HTTP サーバーが配信します。
パフォーマンス
ノートパソコンでも毎秒数万リクエストを処理します。すべてのコアを使う 1 つのプロセスで、RAM は数十 MB です。
起動
ミリ秒単位で起動するので、ゼロまでスケールするプラットフォームでもコストを抑えられます。
デプロイ
コピーするのはファイル 1 つです。インストールするランタイムも依存関係もなく、Mac から Linux や ARM 向けにビルドできます。
開発
小さく、コンパイルの速い言語です。標準ライブラリの HTTP サーバーは、本番環境でそのまま使えます。
速さ
毎秒 60,061 回の訪問を、19.9 MB のメモリで。
計測するのは、投稿とそのコメントを表示する Inertia のページ 1 つです。tug new が作るとおりのアプリが、セッション、CSRF、Content-Security-Policy を含めて、すべてのコアを使う 1 つのプロセスで配信します。
-
60,061 1 秒あたりの訪問数
Inertia のクライアントからのページの要求に JSON で応答します。100 回中 99 回は 4.38 ms 以内です。
-
56,146 1 秒あたりの初回訪問数
ブラウザーからのページの要求に HTML で応答します。100 回中 99 回は 5.09 ms 以内です。
-
19.9 MB 負荷時のメモリ使用量
起動後のアイドル時は 6.4 MB です。
-
15 ms 起動時間
バイナリを起動してから、ページに最初に応答するまで。
-
9.6 MB デプロイ時のサイズ
静的バイナリ 1 つで、ほかにインストールするものはありません。
-
75 ns リクエストごとの追加時間
Go 標準のルーターである ServeMux に、tug の App が加える時間です(1 つのプロセス内で計測)。
に Apple M1 Max、10 コア、macOS 27.0.1 で計測しました。 すべての数値と、その計測方法
- はじめに
tug をインストールし、アプリを作って実行し、ページとフォームを追加します。
- ルーティングとハンドラー
App、ルート、絶対 URL と署名付きリンク、グループ、ミドルウェア(セキュリティヘッダーもその 1 つ)、リクエストに応答したルート、プロキシの背後にいるクライアントのアドレス、
Ctx、ファイル、ダウンロード、ストリームとイベント、リクエストのバインド、エラー。 - ページ
Inertia のページとその props、ルートテンプレートとその nonce、後から計算される props、共有 props、リダイレクト、ページでのダウンロードとイベント、エラーページ、Vite、Inertia の DevTools。
- サーバーサイドレンダリング
初回訪問のページを、アプリと並んで動く Node がサーバーでレンダリングする仕組みと、
ssrパッケージ。 - フォームとセッション
バリデーション、アプリ独自のルール、フォーカスが外れた時点で各フィールドを検証するフォーム、フラッシュメッセージ、セッション、CSRF。
- 多言語対応
tug とアプリの文言を言語ごとのファイルから読み、リクエストの言語で表示する仕組み、
langパッケージ、tug lang。 - ファイル
サイズと種類で検証されるアップロード、
storageパッケージによるアプリのディスクや S3 への保存、公開または署名付きのファイルへのリンク。 - アカウント
SQLite、Postgres、MySQL で動く認証スターターと、その API トークンと通知。
authパッケージと、CC や BCC、添付ファイル、ワンクリックの配信停止リンクに対応したmailパッケージ。 - マイグレーション
SQL ファイルによるデータベースのテーブルの作成と変更。各ファイルは、アプリの起動時か
migrateコマンドで、順番に 1 回ずつ実行されます。次のファイルを作るtug migrate newと、migrateパッケージ。 - 認可
ユーザーがある対象に対して何をしてよいか、
authパッケージのアビリティ、理由を伝える 403 としての拒否、ページのユーザーにできることを props で渡す方法。 - 暗号化
アプリのキー、tug がそのキーで暗号化・署名するもの、アプリ独自の値のための
cryptパッケージ、キーのローテーション。 - バックグラウンドジョブ
リクエストより長くかかる処理のための
queueパッケージ。失敗したときの再実行、スケジュールによる実行(タイムゾーンの指定も可能)、何度投入されても 1 つしか待機しないジョブ、すべてのインスタンスを通じた同時実行数や 1 秒あたりの実行数の制限、最終的な失敗と各実行の結果の通知。 - キャッシュ
計算に時間のかかるものを、アプリのどのインスタンスからも見つけられる場所に保存する
cacheパッケージと、同時に二重に実行してはならない処理のためのロック。 - ブロードキャスト
チャンネル上のイベントを扱う
broadcastパッケージ。イベントはアプリのデータベースを通じて、すべてのインスタンスで開かれているページに届き、ページは変わったものを再読み込みします。 - TypeScript
tug genが Go のコードから書き出す型。 - テスト
アプリのページ、フォーム、アップロード、ログインを Go でテストするための Inertia のクライアントである
tugtestパッケージ、メールのためのmailtest、イベントのテスト。 - デプロイ
バイナリ 1 つ、Dockerfile、環境変数、アプリ自身が提供する HTTPS と Let's Encrypt の証明書、ブラウザーがアプリのページで何をしてよいかを伝えるヘッダー(Content-Security-Policy もその 1 つ)、ログ、メトリクス。
- CLI
tug new、tug dev、tug gen、tug routes、tug lang、tug migrate、tug build、tug keyの詳細。