Templates are files that define a specific pattern and give 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 in your template increases, the programming logic you need to customize increases, making it 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 Go code and the least amount of template (text) needed to understand the Go code.
Before beginning our tutorial, you should have:
You can also clone the guide’s repository to access the complete template files or enter:
git clone https://github.com/Bamimore-Tomi/go-templates-guide.git
In this section, we’ll explore the features of the text/template
package in Go.
ParseFiles
in GoTo work with templates, you must parse them into your Go program.
The text/template
standard library provides the functions needed to parse our program:
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.
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
.
Execute
method in GoThe 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 is 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{}
, which 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.
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.
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 response 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
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:
lt
for the less-than operatorgt
for the greater-than operatoreq
for the equals-o operatorne
for the not equals-to operatorle
for the less-than-or-equal-to operatorge
for the greater-than-or-equal-to operatorNow, 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
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.
Whenever you see your favorite newsletter that is customized to you, chances are an email template and a templating processing package like text/template
in Go made it look that way. The following program will customize a newsletter from the various techniques and methods in the text/template
package.
package main import ( "fmt" "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-08.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") return timeStamp.Format("Mon, 02 Jan 2006") } // Map name formatDate to formatDate function above var funcMap = template.FuncMap{ "formatDate": formatDate, } type customer struct { Username string Subscriptions []string LastLoginDays int SubscriptionDeadline time.Time } func check(e error) { if e != nil { panic(e) } } func main() { customers := []customer{ { Username: "wisman", Subscriptions: []string{"Kikat", "Lollipop", "Marshmallow", "Oreo", "Jelly Bean"}, LastLoginDays: 10, SubscriptionDeadline: time.Now(), }, { Username: "berry blings", Subscriptions: []string{"Honeycomb", "Gingerbread", "Nougat", "Froyo", "Donut"}, LastLoginDays: 5, SubscriptionDeadline: time.Date(2023, 3, 15, 5, 5, 5, 3, time.UTC), }, { Username: "savy", Subscriptions: []string{"Honeycomb", "Gingerbread", "Nougat", "Froyo", "Donut"}, LastLoginDays: 5, SubscriptionDeadline: time.Date(2023, 6, 15, 5, 5, 5, 3, time.UTC), }, } for _, user := range customers { // Create a new file for each customer file, err := os.Create(fmt.Sprintf("output/%v.txt", user.Username)) check(err) // Execute the template for each user into their respective files err = temp.Execute(file, user) check(err) } }
The program executes an email template using the customers’ data.
Hi {{.Username}}, We notice you last logged in {{.LastLoginDays}} days ago. Your current subscriptions: {{range $subscription := .Subscriptions}}{{$subscription}} {{end}} expires on {{formatDate .SubscriptionDeadline}}.
After running the program, it outputs a file for each customer in the array of customers:
We have seen a range of features in the text/template
package with usage examples. All the features implemented here are the same for the html/template
package. Outputs generated from html/template
are safe from cross-site scripting by providing auto-escaping and context-sensitive sanitization of inputs.
When using templates in the web context where the output is HTML, use the html/template
package.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.