Go templates are powerful tools for generating dynamic content in web applications. I’ve extensively used them in production environments and will share practical patterns that enhance template functionality and maintainability.
Template Basics and Setup
Templates in Go come in two main flavors: text/template for general text processing and html/template for web content. The html/template package provides additional security features to prevent XSS attacks.
package main
import (
"html/template"
"os"
)
func main() {
tmpl := template.Must(template.New("example").Parse(`
<div class="user-profile">
<h1>{{.Name}}</h1>
<p>{{.Bio}}</p>
</div>
`))
data := struct {
Name string
Bio string
}{
Name: "John Doe",
Bio: "Software Engineer",
}
tmpl.Execute(os.Stdout, data)
}
Custom Function Integration
Adding custom functions extends template capabilities significantly. I frequently use this pattern to format dates, process text, and perform calculations within templates.
funcMap := template.FuncMap{
"formatDate": func(t time.Time) string {
return t.Format("2006-01-02")
},
"add": func(a, b int) int {
return a + b
},
"multiply": func(a, b float64) float64 {
return a * b
},
}
tmpl := template.New("custom").Funcs(funcMap)
Template Inheritance
Template inheritance provides a clean way to maintain consistent layouts while allowing page-specific content.
const baseTmpl = `
<!DOCTYPE html>
<html>
<head>
<title>{{template "title" .}}</title>
{{template "meta" .}}
</head>
<body>
<header>{{template "header" .}}</header>
<main>{{template "content" .}}</main>
<footer>{{template "footer" .}}</footer>
</body>
</html>
`
const contentTmpl = `
{{define "title"}}Home Page{{end}}
{{define "meta"}}
<meta name="description" content="Welcome to our site">
{{end}}
{{define "content"}}
<h1>Welcome</h1>
<p>{{.Message}}</p>
{{end}}
`
Data Processing and Loops
Templates support sophisticated data processing through range and conditional statements.
const listTmpl = `
<ul class="product-list">
{{range .Products}}
{{if .InStock}}
<li class="in-stock">
<h3>{{.Name}}</h3>
<p>Price: ${{printf "%.2f" .Price}}</p>
</li>
{{else}}
<li class="out-of-stock">
<h3>{{.Name}} (Out of Stock)</h3>
</li>
{{end}}
{{end}}
</ul>
`
Context-Aware Safety
HTML content safety is crucial. The html/template package automatically escapes content, but sometimes we need to mark content as safe.
type Content struct {
Title string
Body template.HTML
Script template.JS
Style template.CSS
}
content := Content{
Title: "Page Title",
Body: template.HTML("<div>Safe HTML content</div>"),
Script: template.JS("console.log('Safe JavaScript')"),
Style: template.CSS("body { background: #f0f0f0; }"),
}
Error Management
Robust error handling ensures template issues don’t crash your application.
func RenderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) {
buf := new(bytes.Buffer)
if err := tmpl.Execute(buf, data); err != nil {
log.Printf("Template execution failed: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
buf.WriteTo(w)
}
Caching and Performance
Template parsing is expensive. Implement caching for better performance.
type TemplateCache struct {
templates map[string]*template.Template
mutex sync.RWMutex
}
func (tc *TemplateCache) Get(name string) (*template.Template, error) {
tc.mutex.RLock()
defer tc.mutex.RUnlock()
if tmpl, ok := tc.templates[name]; ok {
return tmpl, nil
}
return nil, fmt.Errorf("template %s not found", name)
}
func (tc *TemplateCache) Set(name string, tmpl *template.Template) {
tc.mutex.Lock()
defer tc.mutex.Unlock()
tc.templates[name] = tmpl
}
Component-Based Templates
Breaking templates into reusable components improves maintainability.
const componentsTmpl = `
{{define "button"}}
<button class="{{.Class}}" {{if .Disabled}}disabled{{end}}>
{{.Text}}
</button>
{{end}}
{{define "input"}}
<input
type="{{.Type}}"
name="{{.Name}}"
value="{{.Value}}"
{{if .Required}}required{{end}}
>
{{end}}
`
Dynamic Template Loading
Loading templates from files provides flexibility and easier maintenance.
func LoadTemplates(dir string) (*template.Template, error) {
templates := template.New("")
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(path, ".tmpl") {
content, err := os.ReadFile(path)
if err != nil {
return err
}
_, err = templates.New(info.Name()).Parse(string(content))
if err != nil {
return err
}
}
return nil
})
return templates, err
}
These patterns form a comprehensive approach to template management in Go applications. I’ve found them particularly effective in large-scale projects where maintainability and performance are crucial. The key is to balance flexibility with simplicity, ensuring templates remain manageable as your application grows.
Through practical implementation, these patterns can significantly improve code organization and reduce maintenance overhead. They provide a solid foundation for building dynamic, secure, and efficient web applications in Go.
Remember to consider your specific use case when implementing these patterns. Not every application needs all of them, but understanding each pattern helps make informed decisions about template architecture.