The full-stack Go framework that thinks in components.
Write Vue-style single-file components. Ship a single Go binary.
No Node. No JS bundler. No runtime. Just Go + HTMX.
▶ Try it online • Quickstart • Why GMX • The .gmx File • Features • Docs • Roadmap
Write .gmx code and see the generated Go output instantly — no install needed.
GMX is a transpiler framework that compiles .gmx single-file components into production-ready Go applications with HTMX interactivity.
One file. Models, logic, templates, styles — all colocated. One command. A single, dependency-free binary.
todo.gmx → gmx build → ./todo (single binary, ~5MB, serves on :8080)
GMX doesn't hide Go or HTMX. It makes them work together with type safety, auto-generated routes, built-in security, and zero JavaScript.
# Install
go install github.com/kolapsis/gmx/cmd/gmx@latest
# Create your first component
cat > todo.gmx << 'EOF'
<script>
model Task {
id: uuid @pk @default(uuid_v4)
title: string @min(3) @max(255)
done: bool @default(false)
}
service Database {
provider: "sqlite"
url: string @env("DATABASE_URL")
}
func toggleTask(id: uuid) error {
let task = try Task.find(id)
task.done = !task.done
try task.save()
return render(task)
}
</script>
<template>
<ul>
{{range .Tasks}}
<li id="task-{{.ID}}">
<button hx-patch="{{route `toggleTask`}}?id={{.ID}}"
hx-target="#task-{{.ID}}" hx-swap="outerHTML">
{{if .Done}}✓{{else}}○{{end}} {{.Title}}
</button>
</li>
{{end}}
</ul>
</template>
<style>
li { padding: 0.5rem; cursor: pointer; }
.done { text-decoration: line-through; opacity: 0.5; }
</style>
EOF
# Build & run
gmx build todo.gmx # → produces ./todo binary
DATABASE_URL="app.db" ./todo # → serves on :8080
# Or build + run in one step
DATABASE_URL="app.db" gmx run todo.gmxThat's it. You have a working CRUD app with HTMX reactivity, SQLite persistence, input validation, and CSRF protection. In one file.
Building modern web apps means choosing between two extremes:
| JS Frameworks (Next, Nuxt, SvelteKit) | Go Frameworks (Gin, Echo, Fiber) | |
|---|---|---|
| DX | Great (components, hot reload) | Verbose (scattered files) |
| Performance | Runtime overhead, hydration | Fast, but no component model |
| Deployment | Node runtime, Docker images | Single binary ✓ |
| Type safety | Partial (runtime errors) | Strong ✓ |
| Bundle | JS + CSS + sourcemaps | Just a binary |
GMX gives you Vue's developer experience with Go's production characteristics:
- Component colocation — Model, logic, template, style in one file
- Single binary output — No runtime, no Docker, just
scpand run - Type-safe HTMX — Auto-generated routes, validated parameters, no broken links
- Zero JavaScript — HTMX handles interactivity, Go handles everything else
- Built-in security — CSRF, XSS escaping, SQL injection prevention, all automatic
A .gmx file is a single-file component inspired by Vue's SFC format. Everything your feature needs lives in one place.
<script>
// ── Imports ──────────────────────────────────────
import TaskItem from "./components/TaskItem.gmx"
import { sendEmail } from "./services/mailer.gmx"
import "github.com/stripe/stripe-go" as Stripe
// ── Constants & Variables ────────────────────────
const MAX_TASKS = 100
let requestCount: int = 0
// ── Models (auto-generates DB schema + ORM) ─────
model Task {
id: uuid @pk @default(uuid_v4)
title: string @min(3) @max(255)
done: bool @default(false)
priority: int @min(1) @max(5) @default(3)
tenantId: uuid @scoped // ← auto multi-tenancy
author: User @relation(references: [id])
}
// ── Services (infra config, 12-factor) ──────────
service Database {
provider: "sqlite"
url: string @env("DATABASE_URL")
}
service Mailer {
provider: "smtp"
host: string @env("SMTP_HOST")
pass: string @env("SMTP_PASS")
func send(to: string, subject: string, body: string) error
}
// ── Handlers (auto-routed, type-checked) ────────
func createTask(title: string, priority: int) error {
if title == "" {
return error("Title cannot be empty")
}
const task = Task{title: title, priority: priority, done: false}
try task.save()
return render(task)
}
func toggleTask(id: uuid) error {
let task = try Task.find(id)
task.done = !task.done
try task.save()
return render(task)
}
func deleteTask(id: uuid) error {
let task = try Task.find(id)
try task.delete()
return nil
}
</script>
<template>
<form hx-post="{{route `createTask`}}" hx-target="#task-list" hx-swap="beforeend">
<input name="title" placeholder="What needs to be done?" required />
<button type="submit">Add</button>
</form>
<ul id="task-list">
{{range .Tasks}}
<li id="task-{{.ID}}" class="{{if .Done}}done{{end}}">
<button hx-patch="{{route `toggleTask`}}?id={{.ID}}"
hx-target="#task-{{.ID}}" hx-swap="outerHTML">
{{if .Done}}✓{{else}}○{{end}} {{.Title}}
</button>
<button hx-delete="{{route `deleteTask`}}?id={{.ID}}"
hx-target="#task-{{.ID}}" hx-swap="outerHTML"
hx-confirm="Delete this task?">×</button>
</li>
{{end}}
</ul>
</template>
<style>
form { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
li { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; }
.done { text-decoration: line-through; opacity: 0.5; }
</style>| You write | GMX generates |
|---|---|
model Task { ... } |
Go struct + GORM tags + validation + ORM methods + SQL migrations |
func toggleTask(...) |
HTTP handler + route registration + param parsing + CSRF check |
{{route toggleTask}} |
Type-safe URL /api/toggleTask (compile error if function doesn't exist) |
@scoped on tenantId |
WHERE tenant_id = ? injected on every query, automatically |
@min(3) @max(255) |
Server-side validation before any DB operation |
<style> |
Scoped CSS embedded in the binary via go:embed |
import X from "Y.gmx" |
Recursive multi-file resolution, AST merging, template composition |
- Single-file components with
<script>,<template>,<style>sections - Import system — Vue-style default, destructured, and Go native imports
- Multi-file compilation with recursive dependency resolution and circular import detection
- Scoped CSS with automatic class prefixing
- Declarative models with type-safe annotations (
@pk,@unique,@email,@min,@max,@default,@relation) - Auto-generated ORM —
Task.find(id),Task.all(),.save(),.delete() - Multi-tenancy —
@scopedinjects tenant isolation on all queries - Database providers — SQLite & PostgreSQL via service configuration
- Typed route resolution —
{{route "funcName"}}validated at compile time - Auto handler generation — functions become HTTP endpoints with correct methods
- OOB swaps —
render(Task, SidebarCounter)for multi-target updates - Fragment rendering — handlers return HTML partials, not full pages
- CSRF protection — Double-submit cookies, auto-injected in forms and HTMX headers
- XSS prevention — Contextual auto-escaping via Go's
html/template - SQL injection — Parameterized queries only, no string concatenation
- Input validation — Model constraints enforced server-side before every operation
- UUID validation — Path parameters validated before reaching handlers
- Security headers — Middleware with CSP, X-Frame-Options, etc.
- Services — Database, SMTP, HTTP clients, S3 storage as typed declarations
- Environment config —
@env("VAR")with validation, 12-factor compliant - Dependency injection — Services auto-injected into handler context
- Go imports —
import "github.com/pkg" as Aliasmaps directly togo.mod
gmx build— Compile.gmxto a single Go binary (-ofor custom output path)gmx run— Build and execute immediately (pass args after--)gmx fmt— Format.gmxfiles with consistent indentation (-dfor diff mode)- Embedded assets — CSS, templates compiled in via
go:embed - ~5MB binaries — Go's static compilation, nothing extra
- Zero Docker needed —
scp binary server:/ && ./binary
GMX supports three import styles, all inside <script>:
// 1. Component import (like Vue)
// Imports the component's template, models, and styles
import TaskItem from "./components/TaskItem.gmx"
// 2. Destructured import (pick what you need)
// Cherry-pick functions, models, or services from another file
import { sendEmail, MailerConfig } from "./services/mailer.gmx"
// 3. Go native import (use any Go package)
// Adds to go.mod, available with alias in your script
import "github.com/stripe/stripe-go" as StripeImports are resolved recursively — if TaskItem.gmx imports Badge.gmx, it just works. Circular imports are detected at compile time.
GMX Script is a TypeScript-inspired syntax that transpiles to Go. It's intentionally small — not a new language, but a thin layer over Go with better ergonomics for web handlers.
| GMX Script | Generated Go |
|---|---|
let task = try Task.find(id) |
task, err := TaskFind(db, id); if err != nil { return err } |
try task.save() |
if err := TaskSave(db, task); if err != nil { return err } |
return render(task) |
return tmpl.ExecuteTemplate(w, "task", task) |
return error("Not found") |
return fmt.Errorf("Not found") |
let userId = ctx.User |
userId := ctx.User |
"Task: {t.title}" |
fmt.Sprintf("Task: %s", t.Title) |
Error handling uses try (unwrap-or-return), inspired by Rust/Swift. No more if err != nil boilerplate.
.gmx file
│
▼
┌──────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ ┌──────────┐
│ Lexer │ → │ Parser │ → │ Resolver │ → │ Generator │ → │ go build │
│ (tokens) │ │ (AST) │ │ (imports) │ │ (Go code) │ │ (binary) │
└──────────┘ └──────────┘ └───────────┘ └───────────┘ └──────────┘
│ │
▼ ▼
Script Parser gen_models.go
(GMX Script → AST) gen_handlers.go
gen_template.go
gen_imports.go
gen_services.go
gen_vars.go
gen_helpers.go
gen_main.go
gen_components.go
The compiler is fully modular — each phase is independently testable with 91%+ test coverage.
| GMX | Templ + HTMX | Next.js | Laravel | |
|---|---|---|---|---|
| Single-file components | ✅ | ❌ | ✅ | ✅ (Blade) |
| Type-safe routes | ✅ | ❌ | ❌ | ❌ |
| Single binary | ✅ | ✅ | ❌ | ❌ |
| Zero JS needed | ✅ | ✅ | ❌ | ✅ (optional) |
| Auto multi-tenancy | ✅ | ❌ | ❌ | ❌ |
| Built-in CSRF | ✅ | Manual | ✅ | ✅ |
| Auto ORM from schema | ✅ | ❌ | Prisma | Eloquent |
| Component imports | ✅ | ❌ | ✅ | ✅ |
| No runtime deps | ✅ | ✅ | ❌ | ❌ |
| Learning curve | Low | Medium | High | Medium |
Todo app, SQLite, single machine, 20 rows.
Les valeurs des autres stacks sont des ordres de grandeur issus de benchmarks publics dans des conditions similaires (todo app, SQLite, single core). Le bottleneck write (~315 req/s) vient de SQLite (write lock global), pas de Go. Avec Postgres, ce chiffre monterait facilement a ~5 000+ req/s.
your-app/
├── app.gmx # Main component (entry point)
├── components/
│ ├── TaskItem.gmx # Reusable component
│ └── Navbar.gmx
├── services/
│ └── mailer.gmx # Shared service + functions
└── .env # Environment variables
gmx build app.gmx # → produces ./app binary
gmx build -o server app.gmx # → produces ./server binary
gmx run app.gmx # → build + run immediately
gmx fmt app.gmx components/*.gmx # → format files in placeFull documentation available at kolapsis.github.io/gmx or locally:
pip install mkdocs-material
mkdocs serve
# → http://127.0.0.1:8000Guides: Getting Started, Components, Models, Script, Templates, Services, Security
Contributing: Architecture, AST Reference, Lexer & Parser, Generator, Script Transpiler, Testing
- Lexer with unicode, line/col tracking, all operators
- Section-aware parser (model, service, func, let/const, import)
- GMX Script transpiler (let, try, if/else, render, error, ctx)
- Code generator (models, handlers, templates, routes, main)
- Service infrastructure (SQLite, PostgreSQL, SMTP, HTTP)
- Security (CSRF, XSS, SQL injection, UUID validation, headers)
- Import system (Vue-style, destructured, Go native)
- Multi-file compilation with recursive resolution
- Scoped CSS
-
gmx dev— File watcher + live reload - Background tasks (
@async,@cron) - OOB swap generation (
render(A, B)→ concatenated HTML) - Tailwind JIT integration
-
gmx init— Project scaffolding - Source maps (GMX line → Go line)
GMX is open source and contributions are welcome.
git clone https://github.com/kolapsis/gmx.git
cd gmx
go test ./... # Run all tests (~91% coverage)
go build -o gmx ./cmd/gmx # Build the compiler
# Usage
./gmx build app.gmx # Compile .gmx → binary
./gmx run app.gmx # Build + run immediately
./gmx fmt app.gmx # Format .gmx filesThe codebase is structured for clarity: internal/compiler/ contains the lexer, parser, resolver, script transpiler, and generator — each with comprehensive tests.
See CONTRIBUTING.md for architecture details.
Apache 2.0 — see LICENSE
The code generated by GMX belongs entirely to you. The Apache 2.0 license applies only to the GMX compiler itself.
Stop shipping JavaScript. Start shipping binaries.
Get started →

