Learn and Use Templates in Go
While developing several full stack apps over the years, I realized some apps don’t need a full-fledged frontend application running. Most of these apps serve static content with dynamic values fitted here and there. For example, consider your social media feed, each post of a certain type looks same but it gets populated with data specific to that user. They use some sort of templating to achieve it.
What are templates?
Templates are essentially text files that are used to create dynamic content. For instance, the following JavaScript function takes “name” as an argument and produces different strings.
We can also use the same principle to generate web pages in Go. Web templates allow us to serve personalized results to users. Generating web templates using string concatenation is a tedious task. It can also lead to injection attacks.
Templates in go
There two packages in go to work with templates:
- text/templates (Used for generating textual output)
- html/templates (Used for generating HTML output safe against code injection)
Both of them basically have the same interface with subtle web-specific differences in the latter like encoding script tags to prevent them from running, parsing maps as JSON in view, and more.
Our first Template
- The extension of a template file could be anything. We are using
.gohtml
so that it is supported development tools across IDEs. - “Actions” — data evaluations or control structures — is delimited by
{{
and}}
. The data evaluated inside it is called a Pipeline. Anything outside them is sent to the output unchanged.
You can parse multiple files on line:10 as comma-separated strings (path to file) or parse all files inside a directory. For example:
tpl, err := template.ParseFiles(“index1.gohtml”, “index2.gohtml”)
tpl, err := template.ParseGlob("views/templates/*")
- It returns a template container (here called tpl) and error. We can use our data to execute tpl by calling method Execute. In the case of multiple templates, the name of the template will be passed as 2nd argument while data being 3rd.
- We define our data structure (here type struct). It could be anything in go slice, map, struct, slice-of-structs, structs-of-slice of structs. We will see each of them shortly.
- The data is fetched using dot (aka cursor). We use it to access variables of data inside templates. Remember the identifiers in provided data have to start with Capital case. We can use them to initialise a variable inside Actions like
$myCoupon := .Coupon
. - We can output the execution result to the web page or standard output because template Execute method takes any value which implements type Writer interface.
It renders as plain text in output console, but if we use it to send as a response to a web request, it will be rendered as a web page enabling us to use HTML tags.
It is preferred to do the parsing job inside init function and execution at required places.
func init() {
// Must is a helper that wraps a function returning
// (*Template, error) and panics if the error is non-nil.
tpl = template.Must(template.ParseGlob(“templates/*”))
}
Using different data structures in web templates
Slice (or Array)
Let’s consider a slice of strings:
This can be used in a template by ranging over the Slice (also array) inside Actions. If the value of the pipeline has length zero, nothing will be executed
otherwise, dot (aka cursor) is set to the successive elements of the Slice (also array) and the template is executed.
Map
Let’s consider a Map data structure:
The value of the pipeline inside Action is a map. If there are no key values pairs in the map, nothing is output, otherwise, dot (aka cursor) is set to the successive elements of the map and template is executed with $key
taking each key, $val
taking the respective value. If keys are of the basic type with a defined order ("comparable"), the elements will be visited in sorted key order.
Struct
Let’s consider a struct with a collection of fields as declared inline:
The name of a field of the data struct, preceded by a period, such as .Name
is the argument in the template. The result is the value of the field. Field invocations may be chained: .Field1.Field2
. Fields can also be evaluated on variables, including chaining: $x.Field1.Field2
. The following template shows the fields stored in variables and then evaluated in Actions.
Slice of structs
Let’s consider a slice of structs :
We use a range to iterate over a slice. The value of the pipeline in Action must be an array (or slice). If the value of the length of the slice is zero, nothing is output; otherwise, dot (aka cursor) is set to the successive elements of the array, slice and Template is executed.
Different data structures in go can be combined to make useful templates.
A note on Conditionals in templates:
It is also possible to write conditionals templates
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
like this. This gives us amazing abilities to generate dynamic content. If value of pipeline is empty (that is, false, 0, any
nil pointer or interface value, and any array, slice, map, or
string of length zero), T0 is executed else T1 is executed.
Function In templates
A function in a Go template we can be used if it is defined. By default, no functions are defined in the template, but the Funcs
method on a template can be used to add function by creating mappings like this:
template.Must(template.New(“”).Funcs(fm).ParseFiles(“index.gohtml”))
To create a mapping from names to functions we need to use type FuncMap
. Each function must have either a single return value or two return values of which the second has type error.
type FuncMap map[string]interface{}
On line:7, we have defined a mapping of function monthDayYear
to name fdateMDY
. Now this function can be used inside the template. Remember dot (aka cursor) holds the data provided to template.
These are various ways we can use templates in Go. I will publish how to use what we learned to build our own working web pages using nested templates. You can drop me hello with questions and feedback on twitter. Thanks!