Serve HTML from templates with routes generated by Muxt.
Prerequisites: Go 1.26 or later, go install github.com/typelate/muxt@latest, and $(go env GOPATH)/bin on your PATH.
mkdir hello-muxt && cd hello-muxt
go mod init example.com/hello-muxtpackage main
import (
"embed"
"html/template"
"log"
"net/http"
)
//go:embed *.gohtml
var templateFiles embed.FS
//go:generate muxt generate
var templates = template.Must(template.ParseFS(templateFiles, "*.gohtml"))
func main() {
mux := http.NewServeMux()
TemplateRoutes(mux, nil)
log.Fatal(http.ListenAndServe(":8080", mux))
}Muxt reads the package-level templates variable. TemplateRoutes takes the receiver whose methods routes call. No route here calls one, so RoutesReceiver is empty and nil satisfies it.
{{define "GET /"}}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Hello</title></head>
<body>
<h1>Hello from Muxt!</h1>
</body>
</html>
{{end}}The template name is the route.
go generate && muxt check && go run .muxt check type-checks each template against its handler (check). Open http://localhost:8080.
go generate wrote template_routes.go. Search it for HandleFunc: one call per route template, the template name as the pattern.
The template data carries the request as .Request. Add to index.gohtml:
{{define "GET /hello/{name}"}}
<h1>Hello, {{.Request.PathValue "name"}}!</h1>
{{with .Request.URL.Query.Get "from"}}<p>Sent from {{.}}.</p>{{end}}
{{end}}go generate && muxt check && go run .- http://localhost:8080/hello/world renders "Hello, world!"
- http://localhost:8080/hello/world?from=Go adds "Sent from Go."
template_routes.go also gained ReadHelloByName, a URL builder (.Path).
Add a fragment route to index.gohtml:
{{define "GET /number"}}
<div id="big-number">
<p style="font-size:8rem;text-align:center">{{.Request.URL.Query.Get "n"}}</p>
</div>
{{end}}Replace the GET / template:
{{define "GET /"}}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Hello</title></head>
<body>
<h1>Hello from Muxt!</h1>
<form method="get" action="/number#big-number" target="htmz">
<input type="number" name="n" value="42">
<button>Show</button>
</form>
<div id="big-number">
<p style="font-size:8rem;text-align:center">?</p>
</div>
<iframe hidden name=htmz onload="setTimeout(()=>document.querySelector(contentWindow.location.hash||null)?.replaceWith(...contentDocument.body.childNodes))"></iframe>
</body>
</html>
{{end}}target="htmz" names the iframe, and the #big-number fragment names the element htmz replaces.
go generate && muxt check && go run .Open http://localhost:8080, type a number, click Show. The GET /number handler in template_routes.go is identical in shape to GET /.
Routes here read .Request because none calls a method. Name one and the template reads its result as .Result (call results):
{{define "GET /square/{n} Square(ctx, n)"}}{{.Result}}{{end}}- Template names: methods, status codes, and the full syntax.
- Find untested template behavior: which parts of the HTML your tests hold you to.
- Structure a project for testing: keep the receiver out of
package mainso tests can fake it.