Within the Go Standard library HTML package there’s a template package which enables generating HTML page from a rich templating engine with the output safe against code injection. This means there is no need to worry about XSS attacks as Go parses the HTML template and escapes all inputs before displaying it to the browser.
Let’s take a look at a simple example. This example will use a couple of structs to store data about some people and then display that data in an HTML table.
First let’s look at the structures and how the page data will be generated.
type PersonData struct {
Name string
Occupation string
DOB string
FavoriteColor string
}
type PageData struct {
PageTitle string
People []PersonData
}
pageData := PageData{
PageTitle: "Golang HTML Template Example",
People: []PersonData{
PersonData{
Name: "Tasha F. Weller",
Occupation: "Administrative specialist",
DOB: "August 20, 1949",
FavoriteColor: "Black",
},
PersonData{
Name: "Christina K. Deshotel",
Occupation: "Graphic designer",
DOB: "September 14, 1955",
FavoriteColor: "Green",
},
PersonData{
Name: "Gayle C. Kim",
Occupation: "Ecologist",
DOB: "November 6, 1974",
FavoriteColor: "Blue",
},
PersonData{
Name: "Miguel Y. Denny",
Occupation: "Violin repairer",
DOB: "December 17, 1999",
FavoriteColor: "Blue",
},
},
}
Control Structures
The templating language contains a rich set of control structures to render your HTML. Here you will get an overview of the most commonly used ones. To get a detailed list of all possible structures visit: text/template
Control Structure | Definition |
---|---|
{{/* a comment */}} | Defines a comment |
{{.}} | Renders the root element |
{{.Title}} | Renders the “Title”-field in a nested element |
{{if .True}} {{else}} {{end}} | Defines an if-Statement |
{{range .People}} {{.}} {{end}} | Loops over all "People" and renders the data from each using {{.Name}}, {{.Occupation}}, {{.DOB}}, {{.FavoriteColor}} |
{{block "content" .}} {{end}} | Defines a block with the name “content” |
Parsing Templates
Templates can either be parsed from a string or a file on disk. Below is an example of each.
From file:
tmpl, err := template.ParseFiles("layout.html")
// or
tmpl := template.Must(template.ParseFiles("layout.html"))
From string:
var layout := `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
</body>
</html>`
tmpl := template.Must(template.New("tpl").Parse(layout))
Execute the Template in a Request Handler
Once the template is parsed from disk or string it’s ready to be used in the http request handler.
The Execute function accepts an io.Writer for writing the template out and an
interface{} which passes data into the template. If the function is called
from within an http.ResponseWriter a Content-Type header of text/html
is
automatically set in the HTTP response.
func tmplServer(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("tpl").Parse(layout))
// ...
}
The complete example
package main
import (
"html/template"
"log"
"net/http"
)
type PersonData struct {
Name string
Occupation string
DOB string
FavoriteColor string
}
type PageData struct {
PageTitle string
People []PersonData
}
func main() {
http.HandleFunc("/", tmplServer)
log.Println("Starting on :8080")
http.ListenAndServe(":8080", nil)
}
func tmplServer(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("tpl").Parse(layout))
pageData := PageData{
PageTitle: "Golang HTML Template Example",
People: []PersonData{
PersonData{
Name: "Tasha F. Weller",
Occupation: "Administrative specialist",
DOB: "August 20, 1949",
FavoriteColor: "Black",
},
PersonData{
Name: "Christina K. Deshotel",
Occupation: "Graphic designer",
DOB: "September 14, 1955",
FavoriteColor: "Green",
},
PersonData{
Name: "Gayle C. Kim",
Occupation: "Ecologist",
DOB: "November 6, 1974",
FavoriteColor: "Blue",
},
PersonData{
Name: "Miguel Y. Denny",
Occupation: "Violin repairer",
DOB: "December 17, 1999",
FavoriteColor: "Blue",
},
},
}
tmpl.Execute(w, pageData)
}
var layout string = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{.PageTitle}}</title>
<style>
body {
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
padding: 20px;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
margin-top: 1rem;
margin-bottom: 1rem;
border: 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
table {
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
border: 1px solid #cbcbcb;
}
table td,
table th {
border-left: 1px solid #cbcbcb;
border-width: 0 0 0 1px;
font-size: inherit;
margin: 0;
overflow: visible;
padding: 6px 12px;
}
table td:first-child,
table th:first-child {
border-left-width: 0;
}
table thead {
background: #e0e0e0;
color: #000;
text-align: left;
vertical-align: bottom;
}
table td {
background-color: transparent;
border-bottom: 1px solid #cbcbcb;
}
table tr:nth-child(2n) td {
background-color: #f2f2f2;
}
table tbody > tr:last-child td{
border-bottom-width: 0;
}
</style>
</head>
<body>
<h1>{{.PageTitle}}</h1>
<hr/>
<table>
<thead>
<tr>
<th>Name</th>
<th>Occupation</th>
<th>Date Of Birth</th>
<th>FavoriteColor</th>
</tr>
</thead>
<tbody>
{{range .People}}
<tr>
<td>{{.Name}}</td>
<td>{{.Occupation}}</td>
<td>{{.DOB}}</td>
<td>{{.FavoriteColor}}</td>
</tr>
{{end}}
</tbody>
</table>
<hr/>
<p>Data from <a href="https://www.fakenamegenerator.com">Fake Name Generator</a></p>
</body>
</html>`