始めに
Webアプリ開発って、正直つらくないですか?
- バックエンド(Go / Python / etc)
- フロントエンド(React / Vue / etc)
- API設計
- 状態管理
「やりたいことはシンプルなのに、技術スタックが重すぎる」
そんな違和感から、Goだけで完結するフルスタックフレームワークを作りました。
Marionetteとは
Marionetteは、
Goコードからフロントエンドを“操る”フルスタックフレームワーク
です。
名前の由来は「操り人形(Marionette)」
GoがUIを操作する様子が、そのままコンセプトになっています。
コンセプト
Marionetteの思想はかなりシンプルです。
✅ 1. Goだけで完結
- フロントエンド
- バックエンド
- 状態管理
- 通信
すべてGoで記述できます。
👉 フロントエンド言語不要
✅ 2. 宣言的UI(React風)
GoでUIをこう書けます:
mf.Stack(
mf.PageHeader(...),
mf.ActionForm(...),
mf.Region(...),
)
- JSXなし
- TypeScript不要
- でも構造はReactライク
✅ 3. htmxベースの部分更新
フルSPAではない
サーバー駆動UI
必要な部分だけ更新
👉 シンプルで高速
✅ 4. Hot Reload対応
開発時は変更が即反映されます。
何が嬉しいのか?
従来
| 項目 | 技術 |
|---|---|
| フロント | React |
| API | Go / Node |
| 状態管理 | Redux |
| 通信 | REST / GraphQL |
👉 分断される
Marionette
| 項目 | 技術 |
|---|---|
| すべて | Go |
👉 1つの言語で統一
サンプル:タスク管理アプリ
インストール
go get github.com/YoshihideShirai/marionette@latest
main.go
package main
import (
"strings"
mb "github.com/YoshihideShirai/marionette/backend"
mf "github.com/YoshihideShirai/marionette/frontend"
)
func main() {
app := buildApp("Simple Tasks", "Marionette end-to-end sample")
if err := app.Run("127.0.0.1:8081"); err != nil {
panic(err)
}
}
type task struct {
ID int
Name string
}
func buildApp(title, description string) *mb.App {
app := mb.New()
app.SetGlobal("tasks", []task{})
app.SetGlobal("nextID", 1)
app.Page("/", func(ctx *mb.Context) mf.Node {
return page(ctx, title, description)
}, mb.WithTitle(title))
app.Action("tasks/create", func(ctx *mb.Context) mf.Node {
name := strings.TrimSpace(ctx.FormValue("name"))
if name != "" {
nextID := ctx.IncrementGlobalInt("nextID", 1) - 1
ctx.UpdateGlobal("tasks", func(old any) any {
tasks := append([]task(nil), old.([]task)...)
return append(tasks, task{ID: nextID, Name: name})
})
}
return taskList(ctx.GetGlobal("tasks").([]task))
})
return app
}
func page(ctx *mb.Context, title, description string) mf.Node {
tasks := ctx.GetGlobal("tasks").([]task)
return mf.Container(mf.ContainerProps{MaxWidth: "4xl", Centered: true},
mf.Stack(mf.StackProps{Direction: "column", Gap: "6"},
mf.PageHeader(mf.PageHeaderProps{
Title: title,
Description: description,
}),
mf.ActionForm(mf.ActionFormProps{
Action: "/tasks/create",
Target: "#task-list",
Swap: "innerHTML",
Props: mf.ComponentProps{Class: "space-y-3"},
},
mf.FormRow(mf.FormRowProps{
ID: "task-name",
Label: "Task",
Required: true,
Control: mf.TextField(mf.TextFieldProps{
ID: "task-name",
Name: "name",
Placeholder: "Task name",
Required: true,
}),
}),
mf.SubmitButton("Add Task", mf.ComponentProps{}),
),
mf.Region(mf.RegionProps{ID: "task-list"}, taskList(tasks)),
),
)
}
func taskList(tasks []task) mf.Node {
if len(tasks) == 0 {
return mf.EmptyState(mf.EmptyStateProps{Title: "No tasks yet"})
}
rows := make([]mf.TableComponentRow, 0, len(tasks))
for _, t := range tasks {
rows = append(rows, mf.TableRowValues(t.ID, t.Name))
}
return mf.Table(mf.TableProps{
Columns: []mf.TableColumn{{Label: "ID"}, {Label: "Name"}},
Rows: rows,
})
}
ポイント解説
🧠 状態管理
app.SetGlobal("tasks", ...)
ctx.GetGlobal("tasks")
👉 サーバー側に状態を持つ
🔁 UI更新
app.Action("tasks/create", ...)
👉 アクション実行 → 部分更新
🧩 UI構築
mf.Container(
mf.Stack(...),
)
👉 完全にGoでUI構築
どんな用途に向いているか?
👍 向いている
- 管理画面
- 内製ツール
- プロトタイプ
- 小〜中規模アプリ
- Streamlitの代替
🤔 向いていない
- 大規模SPA
- 超リッチUI
- フロント分業前提のチーム
類似技術との比較
| 技術 | 特徴 |
|---|---|
| Streamlit | PythonでUI、でも自由度低い |
| React | 高機能、でも重い |
| HTMX | シンプルだが構造管理が弱い |
| Marionette | Goで統一 + UI DSL |
今後の展望
- コンポーネント拡張
- テンプレート機能
- デプロイ周りの整備
- WebView対応強化
まとめ
Marionetteは、
「GoだけでWebアプリを書きたい」
というニーズに対する、かなりストレートな解です。
- フロントエンド 不要
- API設計 不要
- 状態管理も統一
👉 思考コストが劇的に下がる
おわりに
まだまだ発展途上ですが、
コンセプトとしてはかなり面白い領域だと思っています。
ぜひ触ってみてください👇
👉 https://github.com/YoshihideShirai/marionette
フィードバック・Issue・Starお待ちしてます 🙌