hakk

software development, devops, and stuff
Tree lined path

HTML templating in Go

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 StructureDefinition
{{/* 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>`