How to Use Templates in Go
Go (Golang) comes with a powerful, versatile templating system that allows for dynamic output generation, whether it's textual information (such as an email, document, or simply a console command) or entire web pages.
Template processing in Go is based on two primary packages, each serving its own purpose:
text/template
html/template
It’s important to note that both packages have an identical interface; however, the second (html/template) automatically protects HTML output from certain types of attacks, such as injections.
Converting a Go template into final output is done by applying the template to the appropriate data structure. The input text for Go templates can be in any format and encoded in UTF-8.
Template Entities
A template is generally associated with a specific data structure (e.g., a struct) whose data will be inserted into the template.
Thus, any template formally consists of three basic types of entities that "extract" the necessary variables and insert them into the output:
ActionsThese are text fragments enclosed in curly braces {{ }}, where calculations or data substitutions take place. Actions make the content within the template dynamic by inserting the appropriate data. Actions can include simple variable substitutions, as well as loops or function executions that contribute to the final output. They directly control how the final result will appear.
ConditionsConditions are the classic if-else constructions used within the template. Conditions allow you to include or exclude entire blocks of text from the final output, significantly enhancing the flexibility and capability of template-based content generation.
LoopsLoops allow you to iterate over a collection of data, outputting multiple similar blocks but with different key information. These are useful when you need to generate repeated elements based on a list or array.
Managing Templates in Go
In Go, there are three most commonly used functions for managing templates:
New: Creates a new template, which must be defined later.
Parse: Analyzes the provided string containing the template text, and then returns a ready-to-use template.
Execute: Executes the parsed template, applying the provided data structure, and writes the result to a specified variable.
Additionally, there is the ParseFiles function, which allows you to process entire files containing the template's content rather than just a string.
The following code demonstrates how to use these basic template functions in a simple scenario:
package main
// In addition to the template package, we import "os", which provides a platform-independent interface for interacting with the operating system. In this case, we'll use it to output the result of the template execution to the console.
import (
"os"
"text/template"
)
// Define a struct whose data will be inserted into the template.
type Person struct {
Name string
Age int
}
func main() {
some_person := Person{"Alex", 32} // Instance of the previously defined struct
some_template := "This is {{ .Name }} and he is {{ .Age }} years old" // Template text with embedded actions inside curly braces
// Create a new template and parse its content, preparing it for further use
ready_template, err := template.New("test").Parse(some_template)
// Check for errors (nil means no error, similar to null in C)
if err != nil {
panic(err) // Stop execution and print the error
}
// Execute the template and print the result to the console
err = ready_template.Execute(os.Stdout, some_person) // OUTPUT: This is Alex and he is 32 years old
// Check for errors again
if err != nil {
panic(err) // Stop execution and print the error
}
}
You can reuse a template "compiled" using the Parse function but with data from a different structure. For example, you could continue the main function from the above code like this:
// Continuing from the previous code
...
another_person := Person{"Max", 27} // Create another instance of the struct
err = ready_template.Execute(os.Stdout, another_person)
}
In this case, the template will be reused and applied to the new instance (another_person), producing different output based on the new data.
Note that inside a template, variables from the structure that was passed during execution are referenced within double curly braces {{ }}. When referring to these variables, the structure name is implicitly omitted and only the variable name is used, prefixed with a dot.
For example:
This is {{ .Name }} and he is {{ .Age }} years old
You can also directly access the data passed during execution. For instance, the following code demonstrates how to pass simple text directly into the template:
package main
import (
"os"
"text/template"
)
func main() {
some_template := "Here we have {{ . }}"
ready_template, err := template.New("test").Parse(some_template)
if err != nil {
panic(err)
}
ready_template.Execute(os.Stdout, "no data, only text") // OUTPUT: Here we have no data, only text
}
In this example, the template simply inserts whatever value was passed to it (in this case, "no data, only text") without referencing a structure or fields within a structure.
Template Syntax Features
Static Text
In the simplest case, a template can simply output static text without using any additional data:
import (
"os"
"text/template"
)
...
some_template := "Just regular text"
ready_template, err := template.New("test").Parse(some_template)
if err != nil { panic(err) }
ready_template.Execute(os.Stdout, "no data") // OUTPUT: Just regular text
Static Text Inside Actions (Curly Braces)
You can combine regular static text with additional data within curly braces:
import (
"os"
"text/template"
)
...
some_template := "Not just regular text with {{ \"additional\" }} data" // Don't forget to escape the double quotes
ready_template, err := template.New("test").Parse(some_template)
if err != nil { panic(err) }
ready_template.Execute(os.Stdout, "no data") // OUTPUT: Not just regular text with additional data
Trimming Whitespace Markers
You can use trimming markers before and after the curly braces to remove spaces:
...
some_template := "Not just regular text with {{- \"additional\" -}} data"
...
ready_template.Execute(os.Stdout, "no data") // OUTPUT: Not just regular text withadditionaldata
// The output above isn't a typo — the spaces around "additional" have been removed
Trimming markers remove not only a single space but multiple spaces on both sides of the text produced by the code inside the curly braces — both from the inside and outside.
Numbers in Templates
Unlike text, numbers are automatically inserted into the output without needing quotes:
...
some_template := "Maybe this code was written by {{ 5 }} people."
...
ready_template.Execute(os.Stdout, "no data") // OUTPUT: Maybe this code was written by 5 people.
Similarly, trimming markers can be used with numbers as well:
...
some_template := "Maybe this code was written by {{- 5 }} people."
...
ready_template.Execute(os.Stdout, "no data") // OUTPUT: Maybe this code was written by5 people.
Template Variables
Golang allows you to define special variables that are only available within the template itself. Like in Go, a variable is defined by specifying its name and value, and then it is used. To define an internal variable, use $:
package main
import (
"os"
"text/template"
)
func main() {
some_template := "First, we define a variable {{- $some_variable :=`Hello, I'm a variable` }}, then we use it: \"{{ $some_variable }}\""
ready_template, err := template.New("test").Parse(some_template)
if err != nil { panic(err) }
ready_template.Execute(os.Stdout, "no data") // OUTPUT: First, we define a variable, then we use it: "Hello, I'm a variable"
}
Note that to access the variable, we use $ because this variable is not related to any Go data structure, but is defined within the template itself.
Conditional Expressions
Go templates allow branching based on logic using the standard if/else conditional operators found in most programming languages:
package main
import (
"os"
"text/template"
)
func main() {
some_template := "{{ if eq . `hello` -}} Hello! {{ else -}} Goodbye! {{ end }}" // We use a trimming marker after each condition to remove the leading space in the output
ready_template, err := template.New("test").Parse(some_template)
if err != nil { panic(err) }
ready_template.Execute(os.Stdout, "hello") // OUTPUT: Hello!
}
In this example, the eq function is used (which stands for "equal") to compare the value passed to the template (accessed via the dot) with the string hello.
Also, note that every conditional block is terminated with the end keyword.
You can actually simplify the code by skipping the string comparison and directly passing a boolean variable, which makes the code more concise:
package main
import (
"os"
"text/template"
)
func main() {
some_template := "{{ if . -}} Hello! {{ else -}} Goodbye! {{ end }}"
ready_template, err := template.New("test").Parse(some_template)
if err != nil { panic(err) }
ready_template.Execute(os.Stdout, false) // OUTPUT: Goodbye!
}
Loops
Templates are commonly used to output multiple similar data items, where the number of items changes from one output to another. This is where loops come in handy:
package main
import (
"os"
"text/template"
)
func main() {
some_list := []string{"First", "Second", "Third"}
some_template := "Let's count in order: {{ range .}}{{.}}, {{ end }}"
ready_template, err := template.New("test").Parse(some_template)
if err != nil { panic(err) }
ready_template.Execute(os.Stdout, some_list) // OUTPUT: Let's count in order: First, Second, Third,
}
In this example, there's one issue—the last item in the list results in an extra comma followed by a space. To fix this, you can modify the code to check if the item is the last one in the list, ensuring that there is no comma and space after the last item:
package main
import (
"os"
"text/template"
)
func main() {
some_list := []string{"First", "Second", "Third"}
some_template := "Let's count in order: {{ range $index, $element := .}}{{ if $index }}, {{ end }}{{$element}}{{ end }}"
ready_template, err := template.New("test").Parse(some_template)
if err != nil { panic(err) }
ready_template.Execute(os.Stdout, some_list) // OUTPUT: Let's count in order: First, Second, Third
}
In this modified example, two new variables are introduced—$index and $element—which are updated on each iteration of the loop. A comma and space are printed before each element, but only if the index ($index) is not zero. This ensures that the comma is not added before the first element.
Template Functions
Within Go templates, you can define and call custom functions that perform various operations on the passed arguments. However, before using them in a template, they need to be explicitly declared and registered.
Here is an example:
package main
import (
"os"
"text/template"
)
func manipulate(first_arg, second_arg int) int {
return first_arg + second_arg
}
func main() {
some_list := []int{1, 2, 3}
some_template := "Adding index and element in order: {{ range $index, $element := .}}{{ if $index }}, {{ end }}{{$index}} + {{$element}} = {{ do_manipulation $index $element }}{{ end }}"
ready_template, err := template.New("test").Funcs(template.FuncMap{"do_manipulation": manipulate}).Parse(some_template)
if err != nil { panic(err) }
ready_template.Execute(os.Stdout, some_list) // OUTPUT: Adding index and element in order: 0 + 1 = 1, 1 + 2 = 3, 2 + 3 = 5
}
In this example, we intentionally renamed the Go function manipulate inside the template to do_manipulation. This is possible due to Go's flexibility. However, you could also use the original function name by simply registering it like this:
ready_template, err := template.New("test").Funcs(template.FuncMap{"manipulate": manipulate}).Parse(some_template)
This allows the custom function manipulate (or do_manipulation if renamed) to be used within the template for performing operations like addition on the index and element.
Working with HTML Templates in Go
As mentioned earlier, Go has an additional package for working with HTML templates: html/template. Unlike the standard text/template, this package protects applications from cross-site scripting (XSS) attacks, as Go ensures that data is safely rendered without allowing malicious content.
Here’s how to import the necessary packages:
import (
"html/template"
"net/http"
)
The net/http package is required to start an HTTP server on your local machine, which is necessary for testing the next example.
HTML Template File
It's best practice to store the template in a separate file. In this case, we'll create a file with the .html extension, although you can use any extension you prefer in your projects — Go does not impose any restrictions.
We'll call the file index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>{{ .Title }}</h1>
<p> {{ .Text }} </p>
</body>
</html>
Notice that we’ve specified two variables: Title and Text. Their values will be passed from a Go structure into the template.
Minimal Go Code to Serve HTML Template
Now let’s write the minimal Go code to run an HTTP server and send the rendered template result as a response to any request to the server:
package main
import (
"os"
"html/template"
"net/http"
"log"
)
// Declare the structure to store data for generating the template
type Content struct {
Title string
Text string
}
// Function to handle HTTP requests to the server
func generateResponse(writer http.ResponseWriter, request *http.Request) {
if request.Method == "GET" {
some_template, _ := template.ParseFiles("index.html") // Parse the template file
some_content := Content{
Title: "This is the Title",
Text: "This is the text content",
}
err := some_template.Execute(writer, some_content) // Execute the template, writing the output to the response writer
if err != nil {
panic(err)
}
}
}
func main() {
// Start the HTTP server and use the generateResponse function to handle requests
http.HandleFunc("/", generateResponse)
err := http.ListenAndServe("localhost:8080", nil)
if err != nil {
log.Fatalln("Something went wrong:", err)
}
}
Conclusion
The Go programming language provides built-in support for creating dynamic content or rendering customizable output through templates. On our app platform you can deploy Golang apps, such as Beego and Gin.
This article covered the basic template functions that allow you to manage data dynamically, altering it according to a defined pattern — the template description itself.
The implementation involves a few usage options:
text/template
html/template
Remember that every template goes through three stages of formation, each of which is handled by a corresponding function:
New: Creating the template.
Parse: Analyzing (parsing) the template.
Execute: Executing the template. This stage can be repeated indefinitely.
You can refer to the official Go documentation on the text/template and html/template packages for more detailed information on the available functions and ways to use them.
05 December 2024 · 13 min to read