Oluwatomisin Bamimore I'm a full-stack Python developer, technical writer, and a Section Engineering Education contributor.

Using Golang templates

6 min read 1690

Using Golang Templates

Templates are files that define a specific pattern and gives room for automation, be it a simple text file or an HTML file for a webpage. If it provides space for customization, it is a template.

You can write programming logic to parse simple templates, but as the level of customization you want in your template increases, the programming logic you need to customize increases. At this point, it becomes less feasible to write template parsing logic.

Programming languages and web development frameworks support template parsing out of the box or provide libraries for template parsing.

In this article, we will explore features in the Go programming language that support template parsing. We can use these methods to parse any file extension, but we will use only text files in this article.

This guide will also only display Golang code and the least amount of template (text) needed to understand the Golang code.

Golang template tutorial prerequisites

Before beginning our tutorial you should have the following:

You can also clone the guide’s repository to access the complete template files or enter the following:

git clone https://github.com/Bamimore-Tomi/go-templates-guide.git

Working with Golang templates

In this section, we’ll explore the features of the [text/template](https://blog.logrocket.com/tag/go/) package in Go.

Using ParseFiles

To work with templates, you must parse them into your Golang program.

The text/template standard library provides the functions needed to parse our program:

We made a custom demo for .
No really. Click here to check it out.

package main
import (
    "log"
    "os"
    "text/template"
)
// Prints out the template without passing any value using the text/template package
func main() {
    template, err := template.ParseFiles("template-01.txt")
    // Capture any error
    if err != nil {
        log.Fatalln(err)
    }
    // Print out the template to std
    template.Execute(os.Stdout, nil)
}
//OUTPUT
// Hi <no value>
// You are welcome to this tutorial

The program above prints a template file called template-01.txt. The template variable holds the content of the file. To print out the file to Stdout, we call the Execute method.

Using ParseGlob

For parsing multiple files at once, the ParseGlob function is useful:

package main
import (
    "log"
    "os"
    "text/template"
)
// Parse all the files in a certain directory
func main() {
    // This function takes a pattern. It can be a folder
    temp, err := template.ParseGlob("templates/*")
    if err != nil {
        log.Fatalln(err)
    }
    // Simply calling execute parses the first file in the directory
    err = temp.Execute(os.Stdout, nil)
    if err != nil {
        log.Fatalln(err)
    }
    // Or you can execute a particular template in the directory
    err = temp.ExecuteTemplate(os.Stdout, "template-03.txt", nil)
    if err != nil {
        log.Fatalln(err)
    }
    // Calling a template not in the directory will produce an error
    err = temp.ExecuteTemplate(os.Stdout, "template-04.txt", nil)
    if err != nil {
        log.Fatalln(err)
    }
}

With this code, we parsed all the files in the templates/ directory to our program. To execute any of the templates parsed, we call the ExecuteTemplate method on the result of ParseGlob.

Using the Execute method

The Execute method is where we parse data into the template(s). Here is the template-04.txt file:

Hello {{.}}

You are doing great. Keep learning.
Do not stop {{.}}

The {{.}} tells the text/template package where to place the data that passed into the template. In this template, we want to set the data in two places: line 1 and line 4:

package main
import (
    "log"
    "os"
    "text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-04.txt"))
}
func main() {
    // Execute myName into the template and print to Stdout
    myName := "Oluwatomisin"
    err := temp.Execute(os.Stdout, myName)
    if err != nil {
        log.Fatalln(err)
    }
}
// Hello Oluwatomisin
// You are doing great. Keep learning.
// Do not stop Oluwatomisin

Here, we use slightly different syntax to initialize the template. temp.Execute takes an io.writer and data interface{}, that is myName in this case.

You can also pass in more complicated data structures in your template; the only change to do this is how you access the structures inside the template file, which we will discuss in the next section.

Declaring variables inside templates

We can also initialize variables right inside a template. Check the example in template-05.txt:

Hello {{.Name}}

{{$aTrait := .Trait}}
You are {{$aTrait}}

Notice a change in how we use data inside the template. template.Execute takes a data interface{} argument, which means we can execute a struct in a template:

package main
import (
    "log"
    "os"
    "text/template"
)
type PersonTrait struct {
    Name string
    Trait string
}
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-05.txt"))
}
func main() {
    // Execute person into the template and print to Stdout
    person := PersonTrait{Name: "Oluwatomisin", Trait: "a great writer"}
    err := temp.Execute(os.Stdout, person)
    if err != nil {
        log.Fatalln(err)
    }
}
// Hello Oluwatomisin
// You are a great writer

In this example, we execute the PersonTrait struct in the template. This way, you can execute any data type inside a template.

Using loops in Go templates

The text/template package also allows you to run loops inside a template. In template-06.txt, we will list some cute pets:

Animals are cute; some cute animals are:
{{range .}}
{{.}}
{{end}}

In the program, we must execute a slice of cute pets in the template:

package main
import (
    "log"
    "os"
    "text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
    // Execute cuteAnimals into the template and print to Stdout
    cuteAnimals := []string{"Dogs", "Cats", "Mice", "Fish"}
    err := temp.Execute(os.Stdout, cuteAnimals)
    if err != nil {
        log.Fatalln(err)
    }
}
// Animals are cute, some cute animals are:
// Dogs
// Cats
// Mice
// Fish

We can also loop through a map if needed:

Animals are cute, some cute animals are:
{{range $key, $val := .}}
{{$key}} , {{$val}}
{{end}}

Then, we can create and execute the map in the program:

package main
import (
    "log"
    "os"
    "text/template"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
    // Execute cuteAnimalsSpecies into the template and print to Stdout
    cuteAnimalsSpecies := map[string]string{
        "Dogs": "German Shepherd",
        "Cats": "Ragdoll",
        "Mice": "Deer Mouse",
        "Fish": "Goldfish",
    }
    err := temp.Execute(os.Stdout, cuteAnimalsSpecies)
    if err != nil {
        log.Fatalln(err)
    }
}
// Animals are cute, some cute animals are:
// Cats , Ragdoll
// Dogs , German Shepherd
// Fish , Goldfish
// Mice , Deer Mouse

Conditionals in Golang templates

To add more customization to our templates, we can use conditional statements. To use conditionals, we must call the comparison functions inside the template. In this example, we can check whether a random integer is less or greater than 200:

{{if (lt . 200)}}
Number {{.}} is less than 200
{{else}}
Number {{.}} is greater than 200
{{end}}

The (lt . 200) syntax is how we compare the random integer value using lt. Other operators include the following:

  • lt for the less-than operator
  • gt for the greater-than operator
  • eq for the equals-o operator
  • ne for the not equals-to operator
  • le for the less-than-or-equal-to operator
  • ge for the greater-than-or-equal-to operator

Now, we can generate the random value in our main program:

package main
import (
    "log"
    "math/rand"
    "os"
    "text/template"
    "time"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.ParseFiles("template-06.txt"))
}
func main() {
    // Generate random number between 100 and 300
    rand.Seed(time.Now().UnixNano())
    min := 100
    max := 300
    // Execute myNumber into the template and print to Stdout
    myNumber := rand.Intn((max-min)+1) + min
    err := temp.Execute(os.Stdout, myNumber)
    if err != nil {
        log.Fatalln(err)
    }
}
// Number 141 is less than 200

Using functions in templates

The text/template package also provides a way to execute custom functions inside a template. A famous example is converting timestamps into other date formats:

Hi,

Time before formatting : {{.}}
Time After formatting : {{formatDate .}}

The template displays the time before and after parsing with the formatDate function. Observe the syntax that can call the custom function:

package main
import (
    "log"
    "os"
    "text/template"
    "time"
)
// Declare type pointer to a template
var temp *template.Template
// Using the init function to make sure the template is only parsed once in the program
func init() {
    // template.Must takes the reponse of template.ParseFiles and does error checking
    temp = template.Must(template.New("template-07.txt").Funcs(funcMap).ParseFiles("template-08.txt"))
}
// Custom function must have only 1 return value, or 1 return value and an error
func formatDate(timeStamp time.Time) string {
    //Define layout for formatting timestamp to string
    return timeStamp.Format("01-02-2006")
}
// Map name formatDate to formatDate function above
var funcMap = template.FuncMap{
    "formatDate": formatDate,
}
func main() {
    timeNow := time.Now()
    err := temp.Execute(os.Stdout, timeNow)
    if err != nil {
        log.Fatalln(err)
    }
}
// Hi,
// Time before formatting : 2021-10-04 18:01:59.6659258 +0100 WAT m=+0.004952101
// Time After formatting : 09-04-2021

The layout format must follow the timestamp format, in this case, Jan 2 15:04:05 2006 MST; check the method’s documentation for more information.

Here, however, we use template.FuncMap to map a string to the custom function. The function will then be referenced using the string inside the template.

Conclusion

We have seen a range of features in the text/template package with usage examples. All of the features implemented here are the same for the html/template package, however, outputs generated from htlm/template is safe from code injection.

When using templates in the web context where the output is HTML, use the html/template package.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Oluwatomisin Bamimore I'm a full-stack Python developer, technical writer, and a Section Engineering Education contributor.

Leave a Reply