1. go
  2. /standard library
  3. /templates

Working with Templates in Go

Go provides two main packages for template processing: text/template for general text templates and html/template for safe HTML generation. This guide covers both packages and their features.

Basic Templates

Text Templates

func basicTemplate() error {
    // Define template
    const tmpl = `Hello, {{.Name}}!
Age: {{.Age}}
Email: {{.Email}}`
    
    // Parse template
    t, err := template.New("greeting").Parse(tmpl)
    if err != nil {
        return fmt.Errorf("parsing template: %w", err)
    }
    
    // Execute template
    data := struct {
        Name  string
        Age   int
        Email string
    }{
        Name:  "John Doe",
        Age:   30,
        Email: "[email protected]",
    }
    
    if err := t.Execute(os.Stdout, data); err != nil {
        return fmt.Errorf("executing template: %w", err)
    }
    
    return nil
}

HTML Templates

func htmlTemplate() error {
    // Define template
    const tmpl = `
<!DOCTYPE html>
<html>
<head>
    <title>{{.Title}}</title>
</head>
<body>
    <h1>{{.Header}}</h1>
    <p>{{.Content}}</p>
</body>
</html>`
    
    // Parse template
    t, err := template.New("page").Parse(tmpl)
    if err != nil {
        return fmt.Errorf("parsing template: %w", err)
    }
    
    // Execute template
    data := struct {
        Title   string
        Header  string
        Content string
    }{
        Title:   "Welcome",
        Header:  "Hello, World!",
        Content: "This is a sample page.",
    }
    
    if err := t.Execute(os.Stdout, data); err != nil {
        return fmt.Errorf("executing template: %w", err)
    }
    
    return nil
}

Template Features

Control Structures

const tmpl = `
{{if .Success}}
    Operation completed successfully!
{{else}}
    Error: {{.Error}}
{{end}}

{{range .Items}}
    - {{.Name}}: {{.Price}}
{{end}}

{{with .User}}
    Name: {{.Name}}
    Email: {{.Email}}
{{end}}`

Functions and Pipelines

const tmpl = `
{{.Name | printf "Name: %s"}}
{{.Price | printf "%.2f"}}
{{.Description | truncate 50}}
{{.Date | formatDate "2006-01-02"}}`

func setupTemplate() *template.Template {
    funcMap := template.FuncMap{
        "truncate": func(s string, n int) string {
            if len(s) <= n {
                return s
            }
            return s[:n] + "..."
        },
        "formatDate": func(layout string, date time.Time) string {
            return date.Format(layout)
        },
    }
    
    return template.New("example").Funcs(funcMap)
}

Template Files

Loading Template Files

func loadTemplates() error {
    // Load single template
    t, err := template.ParseFiles("templates/page.html")
    if err != nil {
        return fmt.Errorf("parsing template: %w", err)
    }
    
    // Load multiple templates
    t, err = template.ParseFiles(
        "templates/base.html",
        "templates/header.html",
        "templates/footer.html",
    )
    if err != nil {
        return fmt.Errorf("parsing templates: %w", err)
    }
    
    // Load all templates from directory
    t, err = template.ParseGlob("templates/*.html")
    if err != nil {
        return fmt.Errorf("parsing templates: %w", err)
    }
    
    return nil
}

Template Inheritance

<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{template "title" .}}</title>
</head>
<body>
    <header>{{template "header" .}}</header>
    <main>{{template "content" .}}</main>
    <footer>{{template "footer" .}}</footer>
</body>
</html>

<!-- page.html -->
{{define "title"}}Welcome{{end}}

{{define "header"}}
    <h1>Welcome to our site</h1>
{{end}}

{{define "content"}}
    <article>
        <h2>{{.Title}}</h2>
        <p>{{.Content}}</p>
    </article>
{{end}}

{{define "footer"}}
    <p>&copy; 2024 Our Company</p>
{{end}}

Advanced Features

Custom Functions

func setupFunctions() template.FuncMap {
    return template.FuncMap{
        // String operations
        "lower": strings.ToLower,
        "upper": strings.ToUpper,
        "title": strings.Title,
        
        // Date formatting
        "formatDate": func(t time.Time) string {
            return t.Format("2006-01-02")
        },
        
        // Custom logic
        "increment": func(n int) int {
            return n + 1
        },
        
        // Conditional operations
        "ifelse": func(cond bool, v1, v2 interface{}) interface{} {
            if cond {
                return v1
            }
            return v2
        },
    }
}

Nested Templates

func nestedTemplates() error {
    const (
        master = `
Name: {{template "user" .}}
Address: {{template "address" .}}`
        
        userTmpl = `{{define "user"}}
{{.FirstName}} {{.LastName}}
{{end}}`
        
        addressTmpl = `{{define "address"}}
{{.Street}}
{{.City}}, {{.State}} {{.ZIP}}
{{end}}`
    )
    
    // Parse all templates
    t, err := template.New("master").Parse(master)
    if err != nil {
        return err
    }
    
    _, err = t.Parse(userTmpl)
    if err != nil {
        return err
    }
    
    _, err = t.Parse(addressTmpl)
    if err != nil {
        return err
    }
    
    // Execute template
    data := struct {
        FirstName string
        LastName  string
        Street    string
        City      string
        State     string
        ZIP       string
    }{
        FirstName: "John",
        LastName:  "Doe",
        Street:    "123 Main St",
        City:      "Anytown",
        State:     "ST",
        ZIP:       "12345",
    }
    
    return t.Execute(os.Stdout, data)
}

Best Practices

  1. Use HTML Templates for Web Content

    import "html/template"
    
    func renderPage(w http.ResponseWriter, data interface{}) error {
        t := template.Must(template.ParseFiles("templates/page.html"))
        return t.Execute(w, data)
    }
    
  2. Cache Templates

    var templates = template.Must(template.ParseGlob("templates/*.html"))
    
    func renderTemplate(w http.ResponseWriter, name string, data interface{}) error {
        return templates.ExecuteTemplate(w, name, data)
    }
    
  3. Handle Errors

    func handleTemplate(w http.ResponseWriter, data interface{}) {
        err := templates.ExecuteTemplate(w, "page.html", data)
        if err != nil {
            http.Error(w, "Template error", http.StatusInternalServerError)
            log.Printf("template error: %v", err)
        }
    }
    
  4. Use Context

    type PageData struct {
        Title   string
        Content string
        User    *User
        IsAdmin bool
    }
    
    func buildPageData(r *http.Request) PageData {
        user := getUserFromContext(r.Context())
        return PageData{
            Title:   "Welcome",
            Content: "Page content",
            User:    user,
            IsAdmin: user.IsAdmin,
        }
    }
    

Security Considerations

  1. Auto-Escaping

    // HTML template automatically escapes content
    const tmpl = `<p>{{.UserInput}}</p>`
    
  2. Trusted Content

    // Mark trusted HTML as safe
    template.HTML("<strong>Bold text</strong>")
    
    // Mark trusted URLs as safe
    template.URL("https://example.com")
    
  3. Content Security Policy

    func setSecurityHeaders(w http.ResponseWriter) {
        w.Header().Set("Content-Security-Policy",
            "default-src 'self'; script-src 'self'")
    }
    

Next Steps