Exercise: Template Engine
Difficulty - Intermediate
Learning Objectives
- Master text/template and html/template packages
- Learn template syntax and functions
- Practice template composition and inheritance
- Implement custom template functions
- Build safe HTML rendering
Problem Statement
Create a templates package for rendering dynamic content with templates.
Implementation
1package templates
2
3import (
4 "bytes"
5 "html/template"
6 "strings"
7 "time"
8)
9
10// TemplateEngine manages templates
11type TemplateEngine struct {
12 templates *template.Template
13}
14
15// New creates a new template engine
16func New() *TemplateEngine {
17 tmpl := template.New("").Funcs(template.FuncMap{
18 "upper": strings.ToUpper,
19 "lower": strings.ToLower,
20 "title": strings.Title,
21 "formatDate": formatDate,
22 "add": add,
23 "multiply": multiply,
24 "truncate": truncate,
25 })
26
27 return &TemplateEngine{
28 templates: tmpl,
29 }
30}
31
32// Custom template functions
33func formatDate(t time.Time, format string) string {
34 if format == "" {
35 format = "2006-01-02"
36 }
37 return t.Format(format)
38}
39
40func add(a, b int) int {
41 return a + b
42}
43
44func multiply(a, b int) int {
45 return a * b
46}
47
48func truncate(s string, length int) string {
49 if len(s) <= length {
50 return s
51 }
52 return s[:length] + "..."
53}
54
55// ParseString parses a template string
56func ParseString(name, content string) error {
57 _, err := te.templates.New(name).Parse(content)
58 return err
59}
60
61// Render renders a template with data
62func Render(name string, data interface{}) {
63 var buf bytes.Buffer
64 err := te.templates.ExecuteTemplate(&buf, name, data)
65 if err != nil {
66 return "", err
67 }
68 return buf.String(), nil
69}
70
71// Example templates
72const (
73 emailTemplate = `
74Hello {{.Name | upper}},
75
76Thank you for signing up on {{formatDate .SignupDate "January 2, 2006"}}.
77
78Your account details:
79- Email: {{.Email}}
80- Plan: {{.Plan | title}}
81- Users: {{.UserCount}}
82
83{{if gt .UserCount 10}}
84You have a large team! Consider upgrading to Enterprise.
85{{else}}
86You're on the {{.Plan}} plan.
87{{end}}
88
89Best regards,
90The Team
91`
92
93 htmlTemplate = `
94<!DOCTYPE html>
95<html>
96<head>
97 <title>{{.Title}}</title>
98</head>
99<body>
100 <h1>{{.Heading}}</h1>
101
102 {{if .Items}}
103 <ul>
104 {{range .Items}}
105 <li>{{.}}</li>
106 {{end}}
107 </ul>
108 {{else}}
109 <p>No items to display.</p>
110 {{end}}
111
112 <footer>
113 Generated on {{formatDate .Timestamp "Mon Jan 2 15:04:05 MST 2006"}}
114 </footer>
115</body>
116</html>
117`
118
119 reportTemplate = `
120{{define "header"}}
121=== {{.Title}} ===
122Date: {{formatDate .Date "2006-01-02"}}
123{{end}}
124
125{{define "body"}}
126Summary:
127{{range .Items}}
128- {{.Name}}: {{.Value}}
129{{end}}
130
131Total: {{.Total}}
132{{end}}
133
134{{define "report"}}
135{{template "header" .}}
136
137{{template "body" .}}
138{{end}}
139`
140)
141
142// Example usage
143type User struct {
144 Name string
145 Email string
146 SignupDate time.Time
147 Plan string
148 UserCount int
149}
150
151type ReportData struct {
152 Title string
153 Date time.Time
154 Items []struct {
155 Name string
156 Value int
157 }
158 Total int
159}
160
161func Example() error {
162 engine := New()
163
164 // Parse email template
165 if err := engine.ParseString("email", emailTemplate); err != nil {
166 return err
167 }
168
169 // Render email
170 user := User{
171 Name: "alice",
172 Email: "alice@example.com",
173 SignupDate: time.Now(),
174 Plan: "professional",
175 UserCount: 15,
176 }
177
178 output, err := engine.Render("email", user)
179 if err != nil {
180 return err
181 }
182 println(output)
183
184 // Parse HTML template
185 if err := engine.ParseString("html", htmlTemplate); err != nil {
186 return err
187 }
188
189 // Render HTML
190 htmlData := map[string]interface{}{
191 "Title": "My Page",
192 "Heading": "Welcome",
193 "Items": []string{"Item 1", "Item 2", "Item 3"},
194 "Timestamp": time.Now(),
195 }
196
197 htmlOutput, err := engine.Render("html", htmlData)
198 if err != nil {
199 return err
200 }
201 println(htmlOutput)
202
203 // Parse report template with nested templates
204 if err := engine.ParseString("report", reportTemplate); err != nil {
205 return err
206 }
207
208 // Render report
209 reportData := ReportData{
210 Title: "Monthly Report",
211 Date: time.Now(),
212 Items: []struct {
213 Name string
214 Value int
215 }{
216 {"Sales", 1000},
217 {"Expenses", 500},
218 },
219 Total: 500,
220 }
221
222 reportOutput, err := engine.Render("report", reportData)
223 if err != nil {
224 return err
225 }
226 println(reportOutput)
227
228 return nil
229}
Solution
Click to see the complete solution
The implementation demonstrates a production-ready template engine using Go's html/template package. Key concepts:
Template Syntax
1. Variables and Data:
1{{.Name}} <!-- Access field -->
2{{.User.Email}} <!-- Nested field -->
3{{index .Items 0}} <!-- Index into slice/array -->
2. Control Structures:
1<!-- If/Else -->
2{{if .Condition}}
3 Content when true
4{{else}}
5 Content when false
6{{end}}
7
8<!-- Range -->
9{{range .Items}}
10 <li>{{.}}</li>
11{{else}}
12 <p>No items</p>
13{{end}}
14
15<!-- With -->
16{{with .User}}
17 <p>{{.Name}}</p>
18{{end}}
3. Comparisons:
1{{if eq .Status "active"}}Active{{end}}
2{{if ne .Count 0}}Count: {{.Count}}{{end}}
3{{if gt .Price 100}}Expensive{{end}}
4{{if and .IsAdmin .IsActive}}Admin{{end}}
4. Pipelines:
1{{.Name | upper}} <!-- Function call -->
2{{.Date | formatDate "2006-01-02"}} <!-- With argument -->
3{{.Text | truncate 50 | upper}} <!-- Chaining -->
Best Practices
1. Use html/template for Web:
1// Automatically escapes HTML to prevent XSS
2import "html/template" // Not "text/template"
3
4// Input: <script>alert('xss')</script>
5// Output: <script>alert('xss')</script>
2. Custom Functions:
1funcs := template.FuncMap{
2 "upper": strings.ToUpper,
3 "add": func(a, b int) int { return a + b },
4 "formatDate": func(t time.Time, layout string) string {
5 return t.Format(layout)
6 },
7}
8
9tmpl := template.New("").Funcs(funcs)
3. Template Composition:
1<!-- Define reusable templates -->
2{{define "header"}}
3 <header>{{.Title}}</header>
4{{end}}
5
6{{define "footer"}}
7 <footer>{{.Year}}</footer>
8{{end}}
9
10<!-- Use them -->
11{{template "header" .}}
12<main>Content</main>
13{{template "footer" .}}
4. Error Handling:
1var buf bytes.Buffer
2err := tmpl.ExecuteTemplate(&buf, "page", data)
3if err != nil {
4 // Handle template execution error
5 return err
6}
5. Caching Templates:
1var templates = template.Must(template.ParseGlob("templates/*.html"))
2
3func renderTemplate(w http.ResponseWriter, name string, data interface{}) {
4 err := templates.ExecuteTemplate(w, name, data)
5 if err != nil {
6 http.Error(w, err.Error(), http.StatusInternalServerError)
7 }
8}
Security
Auto-Escaping:
1<!-- Safe: html/template auto-escapes -->
2<div>{{.UserInput}}</div>
3
4<!-- Unsafe: bypasses escaping -->
5<div>{{.TrustedHTML | safeHTML}}</div>
Context-Aware Escaping:
1<!-- Different escaping in different contexts -->
2<a href="{{.URL}}">Link</a> <!-- URL escaping -->
3<script>var x = "{{.JSValue}}"</script> <!-- JS escaping -->
4<style>.class { color: {{.Color}} }</style> <!-- CSS escaping -->
Performance
Pre-parse Templates:
1// Parse once at startup, not on every request
2var templates = template.Must(template.ParseFiles(
3 "templates/base.html",
4 "templates/home.html",
5 "templates/about.html",
6))
Use Buffering:
1// Buffer output to catch errors before sending to client
2var buf bytes.Buffer
3if err := tmpl.Execute(&buf, data); err != nil {
4 return err // Template error, don't send partial response
5}
6w.Write(buf.Bytes())
Key Takeaways
- Custom Functions: Extend templates with custom FuncMap
- HTML Safety: html/template auto-escapes to prevent XSS
- Template Composition: Use {{define}} and {{template}} for reuse
- Conditionals: Use {{if}} for conditional rendering
- Loops: Use {{range}} to iterate over slices/maps
- Security: Context-aware escaping protects against injection
- Performance: Pre-parse and cache templates
Related Topics
- Text Processing - Text manipulation
- Web Development - Web servers, templates, and patterns