The Go templates post
2017-02-02 16:43:40 -0500 EST Programming Go
This post is a slightly revised version of my original "The Go templates post" post that was originally posted 26 May 2014 4:52 PM EDT. The information is still accurate, but does not reflect the past 3 years of changes. Keep this in mind. Be sure to read both the
text/template
andhtml/template
package docs for the latest info.
The Go standard library comes with a powerful template system which allows you to generate structured text from some input data. Think of templates as if they were Unix pipes: you feed some data into the template, and it produces a text document or HTML document based on that data.
Unfortunately, it appears a lot of people have trouble grasping the template system; it seems like every day or every other day someone in #go-nuts has a question about templates that indicates they don't really understand templates. Go templates are somewhat complex because there are some rules involved, but they're not particularly hard to grasp once you understand what you're doing. And so I present this tutorial, which aims to demonstrate practical template use and to help you understand how Go templates work.
A Basic Template
Let's learn by jumping right in. In this post, we'll be writing a small program (first a command-line program, and then a web server) that displays information about people. Silly, but informative.
First, let's define a structure for a single person:
type Person struct {
FirstName string
LastName string
Age int
}
Now, let's create a simple template that just displays one person.
{{.FirstName}} {{.LastName}} is {{.Age}} years old.
Templates are just plain text with additional markup. All the markup is stored between {{
and }}
tags.
The data that we feed to a template is called dot. We refer to it in our tags with .
(hence the name). Dot can be pretty much anything: a simple value, a slice, a structure, and so on. If dot is a structure, you can use standard Go variable syntax to access each component of dot, as we do here. (Note that .
doubles as the dot separating structure fields; don't say ..FirstName
.)
Now let's build the template. All templates in Go have to have a name. The name can be anything, but it must be unique. Template names will play an important role later. Once we have a template, we simply parse the template text inside of it:
t, err := template.New("person").Parse( /* template text here */ )
if err != nil {
panic(err)
}
Notice that template.New()
returns a *template.Template
, and that template.Parse()
does too. All the functions that modify a template return a template back, so we simply daisy-chain these invocations.
Once we have a template, we can finally execute it:
err = t.Execute(os.Stdout, Person{
FirstName: "John",
LastName: "Smith",
Age: 22,
})
if err != nil {
panic(err)
}
The two arguments to template.Execute()
are the output, which is an io.Writer
, and dot.
Let's wrap this all together and run it:
package main
import (
"os"
"text/template"
)
type Person struct {
FirstName string
LastName string
Age int
}
const personTemplate = `{{.FirstName}} {{.LastName}} is {{.Age}} years old.`
func main() {
t, err := template.New("person").Parse(personTemplate)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, Person{
FirstName: "John",
LastName: "Smith",
Age: 22,
})
if err != nil {
panic(err)
}
}
Run this program, and you should see
John Smith is 22 years old.
(Don't worry about the lack of newline at the end; we're about to fix that.)
Keep this program handy as you follow this tutorial, as we'll be changing it (rather than me posting complete programs repeatedly).
Templates Can Format Lists of Data
All right, so now we can display information about a person. But what would be far more useful would be to be able to display information about a bunch of people in a structured format.
var people = []Person{
{"John", "Smith", 22},
{"Alice", "Smith", 25},
{"Bob", "Baker", 24},
}
While we could iterate through every member of people
and execute the template each time, the template system provides a much better way: we can pass the whole slice in and have the template itself do the loop!
{{range .}}
{{.FirstName}} {{.LastName}} is {{.Age}} years old.
{{end}}
The {{range}}
tag starts a loop. The {{end}}
tag ends the loop (and a bunch of other block statements as well). {{range}}
takes a single parameter: a slice to loop through. In this case, dot will be the slice, so we simply say that we want to range through dot.
You'll notice the body of the {{range}}
is exactly the same as it was before. That's because {{range}}
changes dot to each member of the slice. No need to fiddle with iteration variables, and things evolve smoothly!
Now edit the above program to add people
, pass it into t.Execute()
instead of the Person
literal, and change the template text. If you did it right, you'll see
John Smith is 22 years old.
Alice Smith is 25 years old.
Bob Baker is 24 years old.
Woah wait, what's with the blank lines? In Go templates, all whitespace is preserved. To get rid of the blank lines, just remove the newline between the {{range}}
and the body. (Keep the one before the {{end}}
; it lets us end with a newline, as we initially expected.)
Template Tags Are Actually Pipelines, and We Can Create Our Own Functions
Remember my Unix pipe analogy from earlier? The same applies to individual tags.
When we said
{{.Age}}
the template system takes that to mean "get the Age
field from dot and return it as the tag's value". However, we can do more complex manipulations:
{{printf "%x" .Age}}
This uses printf
, a builtin function, to format the Age
field as hexadecimal. Try it; it'll work.
Because tags are pipelines, you can chain multiple functions together. For example:
{{.FirstName}}'s full name is {{printf "%s%s" .FirstName .LastName | len}} letters long.
will return the number of letters in the person's full name by piping the result of printf
, which is a string, into the builtin len
function, which does what you might expect.
To be more precise, the result on the left side of the pipeline will be the last argument of the right side. Pipeline arguments can also be parenthesized pipelines. With these two facts, the above is equivalent to
{{.FirstName}}'s full name is {{len (printf "%s%s" .FirstName .LastName)}} letters long.
This pipeline system is extremely powerful, but its power is limited to the various builtin functions. Because there's no way to do integer arithmetic in templates, let's add a function add
which performs addition:
func add(a int, b int) int {
return a + b
}
To add functions to a template, we call the Funcs()
method on the template. This method takes a single parameter, of type template.FuncMap
, which is a map[string]interface{}
whose value is the function. The function can have virtually any format (and as a bonus, if it returns an error
, the template system will halt if a non-nil
error is returned).
t, err := template.New("person").Funcs(template.FuncMap{
"add": add,
}).Parse(personTemplate)
and let's adjust the template to use this function:
{{range .}}{{.FirstName}} {{.LastName}} is {{.Age}} years old.
In 10 years, {{.FirstName}} will be {{add .Age 10}}.
{{end}}
If you did everything right, you should now see
John Smith is 22 years old.
In 10 years, John will be 32.
Alice Smith is 25 years old.
In 10 years, Alice will be 35.
Bob Baker is 24 years old.
In 10 years, Bob will be 34.
Jumping to HTML
All right, now that we know more about templates, it's time to make the switch to HTML! And the best part is that the first change is both trivial AND if you run our program so far, it'll still work: change
"text/template"
in the imports list to
"html/template"
A html/template.Template
is the same as a text/template.Template
, however, all the data processed by tags will be sanitized for safe consumption into HTML documents. Try it: change the FirstName
or LastName
fields of one of the people in our list to contain some HTML special character, like <
, and run the program with both packages. You'll see the text/template
version emits a <
, while the html/template
version emits a <
instead.
The sanitization is also context-sensitive, so it knows not to corrupt things like <
in JavaScript code. See the package documentation for details.
But since we want to move to HTML, we want to move to running a web server. We don't need to worry about specifics; we can just go ahead and use the most basic possible server, writing to the w
parameter instead of to os.Stdout
.
package main
import (
"html/template"
"net/http"
)
type Person struct {
FirstName string
LastName string
Age int
}
var people = []Person{
{"John", "Smith", 22},
{"Alice", "Smith", 25},
{"Bob", "Baker", 24},
}
const personTemplate = `{{range .}}{{.FirstName}} {{.LastName}} is {{.Age}} years old.
In 10 years, {{.FirstName}} will be {{add .Age 10}}.
{{end}}`
func add(a int, b int) int {
return a + b
}
func handler(w http.ResponseWriter, r *http.Request) {
t, err := template.New("person").Funcs(template.FuncMap{
"add": add,
}).Parse(personTemplate)
if err != nil {
panic(err)
}
err = t.Execute(w, people)
if err != nil {
panic(err)
}
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":6060", nil)
}
(Note: in a real program you wouldn't want to write to w
directly: if Execute()
fails, you'll have a partial write and no safe way to redirect the viewer to an error. Instead, you should have the template write to some other writer, such as a bytes.Buffer
, and then copy that to w
. For this blog post, however, I'll write directly to w
to keep things simple.)
But if you run the server now and navigate to http://localhost:6060/ in your web browser, you'll just get unformatted text. Let's add some HTML to the template.
<html>
<head>
<title>People</title>
</head>
<body>
{{range .}}<p>
{{.FirstName}} {{.LastName}} is {{.Age}} years old.
In 10 years, {{.FirstName}} will be {{add .Age 10}}.
</p>
{{end}}
</body>
</html>
Now if you run the server again and refresh your web browser, you should see a basic HTML page.
Notice that our {{range}}
is still the same as it was before; we only added <p>
tags to each line of output.
Try playing around with what we have now: change the page formatting, drop everything into a table, add some functions to print wackier information, etc. If you need to put HTML into your data structure or functions, use the type template.HTML
, which is an alias for string
that causes html/template
not to sanitize the string.
The only caveat is that you will need to recompile the server each time you change something. There is a way around it, though: we can load templates from files.
But First, Associated Templates and Template Names
You can grab template text at runtime from an external file. Go's template packages provide helper functions which do all the work for you, so you don't have to read in the file yourself, etc. However, there's two important notes about the way the template file functions work, and they were the source of my own confusion about templates when I first learned: associated templates and template names.
Go templates can know about other Go templates: you can associate one template with another, and then call that second template by name from the first. In fact, you can associate multiple templates to a single template, so you can build large documents out of smaller pieces. We'll cover this last.
This is also why each template has a name: each template must have a name that is unique across all the other templates it is associated with.
Go templates can also create other templates in the template body itself. These templates also become associated with the template(s) that created it. We'll also cover this last; everything mentioned in this section is going to be combined to form a complete webpage.
Loading Templates from Files
All right, now it's time to load templates from files. Take the personTemplate
string's value (without the quotes and Go declaration) and put it in a file, also called personTemplate
, in the same directory as the one you'll be running the server from.
Now, rather than creating the template with template.New().Parse()
, we will use the function template.ParseFiles()
. For now, let's assume this function takes one argument, a filename. template.ParseFiles()
makes this file into a template whose name is the filename (keep this in mind!) and parses it. So all we have to do to our web server is change
t, err := template.New("person").Funcs(template.FuncMap{
"add": add,
}).Parse(personTemplate)
if err != nil {
panic(err)
}
to
t, err := template.ParseFiles("personTemplate")
if err != nil {
panic(err)
}
t = t.Funcs(template.FuncMap{
"add": add,
})
Now you can rebuild and run the server...
2017/02/02 16:37:34 http: panic serving [::1]:50102: template: personTemplate:2: function "add" not defined
...er, whoops, functions have to be included before the parsing.
So now we have a chicken-and-egg problem: we have no template until we call template.ParseFiles()
, but we need a template to call Funcs()
.
In our case, however, the functions are only going to be used in the actual page content. And in a real website, you'll want to separate the content from the layout. So we now have one last trick up our sleeves...
Templates Can Be Combined to Form Complete Documents
We have already seen that templates are great for presenting information. We can also use templates to give consistent styles and layout to our information. Let's separate the person reporting from the outer HTML (page layout) for our simple case.
This is why ParseFiles()
is plural: if you pass more than one filename, each filename after the first becomes a new template whose name is the respective filename. All these templates are associated with the first, and the first is returned. We won't be using this behavior in this specific example (though see the end of the post for a related challenge).
We're going to split the personTemplate
file into two: layoutTemplate
, which is the overall page layout, and peopleTemplate
, which is just the content (the part that talks about people). Here's layoutTemplate
:
<html>
<head>
<title>{{.Title}}</title>
</head>
<body>
<h1>{{.Title}}</h1>
{{template "content" .Content}}
</body>
</html>
The {{template}}
tag invokes a template by the given name, setting dot to the value of the second argument. So our layoutTemplate
takes a new dot format:
type Page struct {
Title string
Content interface{}
}
Notice that Content
is an interface{}
. This will allow us to not have to worry about all the different types for dot that each page will expect; the template system just uses the runtime type. You can make Content
a []Person
for our example and it will still work.
There is a caveat about {{template}}
: the name of the template must be specified in the template itself; you can't dynamically call templates.
peopleTemplate
is
{{define "content"}}
{{range .}}<p>
{{.FirstName}} {{.LastName}} is {{.Age}} years old.
In 10 years, {{.FirstName}} will be {{add .Age 10}}.
</p>
{{end}}
{{end}}
{{define}}
makes a new template; the second {{end}}
ends it. This step is important: because we use ParseFiles()
to add files, if we forget {{define}}
, our new template will have the same name as the filename. Adding {{define}}
removes the need to worry about the filename.
All right, so we have a layout template and a content template. How do we load them both together? There are several possibilities. We could simply combine the two template strings into a large template string and parse those together, however there's a better way.
- Create a layout
template.Template
variable that will contain just the layout template. - For each page's content template:
- Clone the layout template. This is done with the
Clone()
method. - Add the content template definition. For our case, we'll use the
ParseFiles()
method. As a method,ParseFiles()
associates each new template with the template that calls it. - Execute the resultant template.
- Clone the layout template. This is done with the
Let's also do the template parsing at program startup, rather than on each page request:
var layout, peoplePage *template.Template
func init() {
layout = template.Must(template.ParseFiles("layoutTemplate")).Funcs(template.FuncMap{
"add": add,
})
peoplePage = template.Must(layout.Clone())
peoplePage = template.Must(peoplePage.ParseFiles("peopleTemplate"))
}
template.Must()
is a wrapper function that consumes the error returned by Template.Clone()
and Template.ParseFiles()
and panics if it is not nil
. Remember that each call to ParseFiles()
adds to what was already there.
Finally, we need to remove all the template creation stuff from our serving function and produce the page:
err = peoplePage.Execute(w, Page{
Title: "People",
Content: people,
})
Rebuild and watch the magic.
Now all you have to do is change the template files and restart the server to see the changes; no recompiling needed. If you want to add more pages, you'll need to recompile, but now you should be able to figure out how.
You should now be able to go and build more complex websites using templates for both layout and content presentation. As your site gets larger, you may find ParseFiles()
to be too inconvenient. If that's the case, you can use template.ParseGlob()
to create a dummy template with files matched by a shell pattern, Template.Lookup()
to get a template by name, and Template.AddParseTree(Template.Tree)
to merge two already-loaded templates together. I leave writing this code out as an exercise.
Conclusion
Phew! That was quite a bit of information, wasn't it? But if you did everything correctly, you should now have a web server that uses templates to present a list of people and information about those people.
This tutorial barely scratches the surface of what templates can do. You'll want to read through the text/template
(tip) and html/template
(tip) documentation; hopefully this tutorial makes understanding the documentation easier.
Happy coding!
Thanks to Senjai and Wessie in #go-nuts for comments and suggestions.