xtemplate
Hypermedia web apps from a directory of Go templates.
xtemplate is a Go server that treats templates as handlers: file-based routing, a request-scoped dot context, and first-class static files; no separate application layer.
<!-- contacts.html β GET /contacts (SQL provider configured as .DB) -->
<ul>
{{range .DB.QueryRows `SELECT id, name FROM contacts`}}
<li><a href="/contacts/{{.id}}">{{.name}}</a></li>
{{end}}
</ul>
<!-- contacts/{id}.html β GET /contacts/{id} -->
{{$id := .Req.PathValue `id`}}
{{$c := .DB.QueryRow `SELECT id, name, phone, email FROM contacts WHERE id = ?` $id}}
<form method="POST">
<input name="name" value="{{$c.name}}">
<input name="phone" value="{{$c.phone}}">
<input name="email" value="{{$c.email}}">
<button>Update</button>
</form>
{{define "POST /contacts/{id}"}}
{{$_ := .DB.Exec `UPDATE contacts SET name=?, phone=?, email=? WHERE id = ?`
(.Req.FormValue `name`) (.Req.FormValue `phone`)
(.Req.FormValue `email`) (.Req.PathValue `id`)}}
{{template `/contacts/{id}.html` .}}
{{end}}
No route table, no handler functions. One file for the index. One file for the form and the update. Path parameters, form values, and SQL all hang off the dot context.
Philosophy
Deal with the first-class citizens of the web: paths, requests, HTML responses, and backing data. Templates are expressive enough to be the app. More details in Design.
Highlights
- File-based routing -
admin/settings.htmlservesGET /admin/settings;index.htmlserves the directory. Template semantics - Any method and pattern -
{{define "DELETE /contact/{id}"}}is a route. Instance loading - Loads once, reloads live - parse at startup; swap an immutable instance on change. Design
- Dot context -
.Req,.Resp,.DB,.FS, β¦ per request. Dot context - Safe by default -
html/templateescaping; optional sanitize / trust helpers. Functions - Optimal static files - hashes, SRI, precompressed encodings, long cache. Instance loading
- SSE -
{{define "SSE /path"}}for live updates. Dot context β Flush - Embeddable - CLI, Docker, Caddy plugin, or
http.Handlerlibrary. Deployment modes
Some more patterns:
{{- with $hash := .X.StaticFileHash `/assets/reset.css`}}
<link rel="stylesheet" href="/assets/reset.css?hash={{$hash}}" integrity="{{$hash}}">
{{- end}}
{{- define "SSE /reload"}}{{.Flush.WaitForServerStop}}data: reload{{printf "\n\n"}}{{end}}
<script>new EventSource("/reload").onmessage = () => location.reload()</script>
More complete apps: examples/.
Quick start
| If you want⦠| Start here |
|---|---|
| Step-by-step first app | Getting started tutorial |
| Zero setup container | Docker |
| Local templates + live reload | CLI Β· CLI flags |
| Templates from a Git remote | CLI --controller-type git |
| Automatic HTTPS, auth, proxy | Caddy module |
| Embed in your Go program | Go library |
# CLI (live reload)
go install github.com/infogulch/xtemplate/cmd/xtemplate@latest
mkdir -p templates && echo '<h1>{{.Req.URL.Path}}</h1>' > templates/index.html
xtemplate --listen :8080
# open http://localhost:8080
# Docker
docker run --rm -p 8080:80 \
-v "$PWD/templates:/app/templates:ro" \
infogulch/xtemplate:latest
# Caddy
:8080
route {
xtemplate
}
Full integration map and configs: docs/reference/deployment-modes.md.
All documentation (tutorial, how-tos, reference, design): docs/.
Public users
- infogulch/todoxt - Todos app built with xtemplate and htmx
- infogulch/rssxt - RSS reader
Contributing
Development setup, repo map, and tests: CONTRIBUTING.md (same as docs/contributing.md).
History and license
Evolved from go-htmx and a Caddy-centric prototype into a standalone library with an optional Caddy module. Narrative: Project history. Releases: CHANGELOG.md.
Licensed under the Apache 2.0 license. See LICENSE.