Go templates
Go comes with a built-in html/template
package, that supports variables, loops, functions, and more. This allows us to simplify our handlers by delegating the display logic to a template.
Here we’ve modified our Hello World example to have only one handler for both hellos and goodbyes, by introducing the greeting
URL parameter. The template then uses an {{ if eq .Greeting }}
conditional to determine the type of greeting.
As a finishing touch, we include a FuncMap
defining a Capitalize()
function, to make sure our name is properly capitalized.
package main
import (
"html/template"
"net/http"
"strings"
"github.com/go-chi/chi"
)
var funcMap = template.FuncMap{
"Capitalize": strings.Title,
}
var tpl = template.Must(template.New("greeting").Funcs(funcMap).Parse(`
{{ if eq .Greeting "hello" }}
<p>Hello <strong>{{ .Name | Capitalize }}</strong>!<p/>
{{ else }}
<p>Goodbye <strong>{{ .Name | Capitalize }}</strong>!<p/>
{{ end }}
`))
type tplData struct {
Greeting, Name string
}
func NewGreetingsRouter() *chi.Mux {
router := chi.NewRouter()
router.Get("/{greeting}/{name}", func(w http.ResponseWriter, r *http.Request) {
data := tplData{
Greeting: chi.URLParam(r, "greeting"),
Name: chi.URLParam(r, "name"),
}
tpl.Execute(w, data)
})
return router
}
func main() {
router := chi.NewRouter()
router.Mount("/greeting", NewGreetingsRouter())
http.ListenAndServe("localhost:3000", router)
}
In practice, however, you probably don’t want to define your templates in string variables like this. Instead, you would create separate HTML files and compose them into layouts to avoid duplication and ensure a consistent style.
First, we take our template and put it in a file instead. We wrap it with a {{ define "greeting" }}
, so we can reference it later from the base template.
// templates/greeting.gohtml
{{ define "greeting" }}
{{ if eq .Greeting "hello" }}
<p>Hello <strong>{{ .Name | Capitalize }}</strong>!</p>
{{ else }}
<p>Goodbye <strong>{{ .Name | Capitalize }}</strong>!</p>
{{ end }}
{{ end }}
Then we create a base template called "index"
that references the greeting
template and wraps it in some proper HTML boilerplate markup.
// templates/index.gohtml
{{ define "index"}}
<html>
<head>
<title>{{ .Greeting }}</title>
</head>
<body>
{{ template "greeting" .}}
</body>
</html>
{{ end }}
Our code stays mostly the same, except that Parse()
became ParseFiles()
instead, taking the template files we just created and Execute()
became ExecuteTemplate()
, taking a reference to the "index"
template.
package main
import (
"html/template"
"net/http"
"strings"
"github.com/go-chi/chi"
)
var funcMap = template.FuncMap{
"Capitalize": strings.Title,
}
var tpl = template.Must(template.New("page").Funcs(funcMap).ParseFiles(
"templates/index.gohtml", "templates/greeting.gohtml"))
type tplData struct {
Greeting, Name string
}
func NewGreetingsRouter() *chi.Mux {
router := chi.NewRouter()
router.Get("/{greeting}/{name}", func(w http.ResponseWriter, r *http.Request) {
data := tplData{
Greeting: chi.URLParam(r, "greeting"),
Name: chi.URLParam(r, "name"),
}
tpl.ExecuteTemplate(w, "index", data)
})
return router
}
func main() {
router := chi.NewRouter()
router.Mount("/greeting", NewGreetingsRouter())
http.ListenAndServe("localhost:3000", router)
}