2. Handling HTTP Requests with Gin – HTML Templates

2.6 HTML Templates

Go language uses the built-in <span>html/template</span> package to render HTML templates, providing safe template parsing and execution features to prevent cross-site scripting attacks (XSS). Templates can be created using <span>template.New()</span> and then read and parsed using <span>ParseFiles()</span>.

Gin uses the <span>gin.Context</span> object’s <span>HTML()</span> method to combine HTML templates with data, rendering and returning them to the client. By setting <span>router.LoadHTMLGlob()</span> or <span>router.LoadHTMLFiles()</span>, Gin can easily load templates from a folder and render them with data.

2.6.1 Template Engine

Go language has two built-in template engine packages, one for handling text <span>text/template</span> and the other for generating safe HTML <span>html/template</span>. Their usage is as follows:

  • Template file extensions

Template file extensions are usually <span>tmpl</span> or <span>tpl</span> and the files must be encoded in <span>UTF-8</span>.

  • Template Syntax

In template files, use <span>{{ }}</span> to wrap data that needs to be dynamically replaced.

  • Data Access

The data passed to the template can be accessed using a dot (<span>.</span>). If it is a complex data type, its fields can be accessed using <span>{{.FieldName}}</span>.

  • Raw Output

Except for the content wrapped in <span>{{ }}</span>, all other content in the template will be output as is, without any modification.

The general steps to use the Go language template engine are as follows:

  • 1. Define the Template

Write the template file according to the template syntax rules, defining the structure and placeholders of the template. An example is shown below:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Gin HTML Response</title>
</head>
<body>
    <h1>{{.body}}</h1>
</body>
</html>
  • 2. Parse the Template

<span>html/template</span> package provides various methods to parse templates and obtain template objects. To create a new template object, use the <span>New()</span> method and specify a name:

func New(name string) *Template

Parse template strings. Use the <span>Parse()</span> method to parse template content:

func (t *Template) Parse(text string) (*Template, error)

Parse template files using the <span>ParseFiles()</span> method to parse one or more template files, returning a template object containing the parsed templates:

func (t *Template) ParseFiles(filenames ...string) (*Template, error)

If parsing multiple template files, use the <span>ParseGlob()</span> method to parse matching multiple template files.

func (t *Template) ParseGlob(pattern string) (*Template, error)

For example, if there are template files starting with ‘surpass’ in the current directory, you can use <span>ParseGlob("surpass")</span> to parse all matching template files.

  • 3. Render the Template

<span>html/template</span> package provides <span>Execute()</span> and <span>ExecuteTemplate()</span> methods to render templates. The <span>Execute()</span> method is used to apply data to the template and write the result to the specified <span>io.Writer</span>, defined as follows:

func (t *Template) Execute(wr io.Writer, data any) error

<span>ExecuteTemplate()</span> method is used to render a specified template name in cases where multiple templates are included. Defined as follows:

func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error 

2.6.2 Using the html/template Package

2.6.2.1 Template Example

In Go language, templates can be applied to data structures, that is, using data structures as parameters for templates, to generate HTML files. Placeholders in the template reference elements of the data structure (usually fields of a struct or keys of a map) to control the rendering process and obtain the values to be displayed.

When executing the template, the template engine traverses the template content and uses the dot notation (.) to reference the current data object. Template files must be encoded in <span>UTF-8</span>, and operations in the template are wrapped in <span>{{}}</span> for data processing and flow control; all text outside of operations will be output as is. Operations cannot contain line breaks, but comments can span multiple lines. An example is shown below:

  • Template File
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Go Render HTML</title>
</head>
<body>
    <h1>Hello {{ . }},Welcome to Go Render</h1>
</body>
</html>
  • Example Code
package main

import (
"fmt"
"html/template"
"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
// Template file
 templateFile := "templates/index.tmpl"
// Parse template
 t, err := template.ParseFiles(templateFile)
if err != nil {
  fmt.Printf("Error parsing file %s: %v\n", templateFile, err)
return
 }
// Render template
 name := "Surpass"
 t.Execute(w, name)
}

func main() {
 http.HandleFunc("/render", helloHandler)
 http.ListenAndServe("0.0.0.0:8080", nil)
}

2.6.2.2 Template Syntax

In Go language template syntax, all operations are enclosed in <span>{{}}</span>. In the template, <span>{{ . }}</span> represents the current data object, and when a struct object is passed in, its fields can be accessed using dot notation. An example is shown below:

  • Template File
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Test Go Render Struct Object</title>
</head>

<body>
    <table border="1">
        <tr>
            <td>Name</td>
            <td>{{.Name}}</td>
        </tr>
        <tr>
            <td>Age</td>
            <td>{{.Age}}</td>
        </tr>
        <tr>
            <td>Location</td>
            <td>{{.Location}}</td>
        </tr>
    </table>
</body>

</html>
  • Template Code
package main

import (
"fmt"
"html/template"
"math/rand"
"net/http"
)

type Person struct {
 Name     string
 Age      int
 Location string
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
// Template file
 templateFile := "templates/index.tmpl"
// Parse template
 t, err := template.ParseFiles(templateFile)
if err != nil {
  fmt.Printf("Error parsing file %s: %v\n", templateFile, err)
return
 }
// Render template
 t.Execute(w, Person{
  Name:     "Surpass",
  Age:      rand.Intn(100),
  Location: "China",
 })
}

func main() {
 http.HandleFunc("/render", helloHandler)
 http.ListenAndServe("0.0.0.0:8080", nil)
}

In the template, you can use something like <span>{{.FieldName}}</span> to access fields in a struct, and if it is a map, you can use <span>{{.KeyName}}</span>

2.6.2.2.1 Comments

Template comments are generally as follows:

{{/* This is a comment */}}

Comments can span multiple lines but cannot be nested, and <span>comments must be adjacent to the delimiters {{}}</span>

2.6.2.2.2 Pipes

Pipes are used to produce data structures. In Go language template syntax, you can use the pipe symbol <span>|</span> to connect multiple commands, similar to pipes in Linux systems. The usage is as follows:

{{ pipeline-1 | pipeline-2 | pipeline-3 | ... |pipeline-n }}

The result of the command before the | will be passed as a parameter to the command after it.

2.6.2.2.3 Variables

In templates, you can capture the execution result of a pipeline by initializing a variable. The syntax for variable initialization is as follows:

{{ $variable := pipeline  }}

$variable is the variable name. Declaring a variable does not produce any output.

2.6.2.2.4 Conditional Statements

Conditional statements in Go language templates have the following forms:

  • First Form
{{ if condition }}
 T-0
{{ else }}
    T-1
{{ end }}
  • Second Form
{{ if condition-1 }}
 T-0
{{ else }}
   {{ if condition-2 }}
      T-1
   {{ else }}
      T-2
   {{ end }}   
{{ end }}
2.6.2.2.5 Iteration

In templates, you can use the <span>range</span> keyword to iterate over arrays, slices, maps, or pipelines, with the basic syntax as follows:

  • Basic Form
{{ range data }}
 T0
{{ end }}

If data is empty, no output will be produced.

  • With Else
{{ range data }}
 T0
{{ else }}
 T1
{{ end }}

If data is empty, T1 will be executed.

Example code is shown below:

package main

import (
"fmt"
"html/template"
"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
// Template file
 templateFile := "templates/index.tmpl"
// Parse template
 t, err := template.ParseFiles(templateFile)
if err != nil {
  fmt.Printf("Error parsing file %s: %v\n", templateFile, err)
return
 }

 month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
// month = []string{}
// Render template
 t.Execute(w, month)
}

func main() {
 http.HandleFunc("/render", helloHandler)
 http.ListenAndServe("0.0.0.0:8080", nil)
}
2.6.2.2.6 With

The <span>with</span> keyword is used to execute specific template code when the argument is not empty, with the basic syntax as follows:

{{ with args }}
 T0
{{ else }}
 T1
{{ end }}

If args is empty, T1 will be executed; if not empty, T0 will be executed.

2.6.2.2.7 Comparison Functions

Built-in comparison functions in templates can be used for conditional statements. These functions treat any type of zero value as false, and others as true. Common comparison functions include:

  • eq: represents equal
  • ne: represents not equal
  • lt: represents less than
  • le: represents less than or equal to
  • gt: represents greater than
  • ge: represents greater than or equal to

To simplify multi-parameter equality comparisons, only <span>eq</span> can accept two or more parameters, comparing the first parameter with the subsequent parameters, with the basic syntax as follows:

{{ eq args1 args2 args3 }}

The above syntax is equivalent to <span>args1 == args2 || args1 == args3</span>, and comparison functions only apply to basic types or their alias types; different types of data cannot be compared directly.

2.6.2.2.8 Predefined Functions

Predefined functions are those defined in the template library that can be used directly within <span>{{}}</span>.

Function Name Functionality
and Returns its first empty parameter or the last parameter, i.e., <span>and x y</span> is equivalent to <span>if x then y else x</span>
or Returns the first non-empty parameter or the last parameter, i.e., <span>or x y</span> is equivalent to <span>if x then x else y</span>
not Returns the opposite result of its single boolean parameter
len Returns the integer length of its parameter
index The result is the value pointed to by the first parameter and the remaining parameters, i.e., <span>index x 1 2</span> is equivalent to <span>x[1][2]</span>‘s value
print Equivalent to <span>fmt.Print()</span>
printf Equivalent to <span>fmt.Printf()</span>
println Equivalent to <span>fmt.Println()</span>
html Returns the HTML source equivalent representation of the base parameter’s text representation
urlquery Returns the equivalent query that can be embedded in a URL query of its parameter’s text representation
js Returns the JavaScript equivalent representation of the base parameter’s text representation
call The result is the return value of calling the first parameter, which must be a function type parameter, with the remaining parameters as arguments to that function, e.g., <span>call x.y 1 2</span> is equivalent to <span>x.y(1,2)</span>
2.6.2.2.9 Custom Functions

In templates, functions can also be passed as parameters. Users can use both built-in functions and custom functions. When using custom functions, it is important to note that <span>the function's return value can only be one or one value and one error</span>. The steps to create a custom template function are as follows:

  • Create a FuncMap map, setting the keys of the map to the function names and the values to the actual defined functions

  • Bind the FuncMap to the template

  • Template File

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>Go Template Sample</title>
    </head>
    <body>
        Two ways to use, the effect is the same:
        <div style="background-color: aquamarine;">Function Example (using pipe): {{ . | hello }}</div>
        <div style="background-color: beige;">Function Example (using function): {{  hello . }}</div>
    </body>
</html>
  • Code File
package main

import (
"fmt"
"html/template"
"net/http"
)

func Hello(name string) string {
return fmt.Sprintf("Hello %s", name)
}

func FuncMapHandler(w http.ResponseWriter, r *http.Request) {
 funcMap := template.FuncMap{"hello": Hello}
 t := template.New("index.html").Funcs(funcMap)
if t, err := t.ParseFiles("index.html"); err != nil {
  fmt.Fprintln(w, err)
 } else {
  t.Execute(w, "Surpass")
 }
}

func main() {
 server := http.Server{
  Addr:    ":8080",
  Handler: nil,
 }
 http.HandleFunc("/funcs", FuncMapHandler)
 server.ListenAndServe()
}
2.6.2.2.10 Nested Templates

<span>html/template</span> package also supports nesting templates. Nested templates can be independent files or defined directly in the template content using the <span>define</span> keyword. The syntax for using <span>define</span> is as follows:

{{ define "name" }}
 <!-- Template Content -->
{{ end }}

To execute a defined template in the template, use the <span>template</span> keyword, as shown in the example below:

{{ template "name" }}

Or pass data to the template, as shown in the example below:

{{ template "name" . }}

In Go version 1.6 and above, the <span>block</span> keyword is similar to <span>define</span>, used to define a template block that can be overridden and executed where needed, with the syntax as follows:

{{ block "name" . }}
 <!-- Template Content -->
{{ end }}

The above definition is equivalent to defining the template name and executing it at the current position, as shown below:

{{ define "name" }}
 <!-- Template Content -->
{{ end }}
{{ template "name" . }}

We can demonstrate this with example code, where the template files are as follows:

  • index.html
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>Template Nesting Demonstration</title>
    </head>
    <body>
        <h1>Template Nesting Demonstration</h1>
        <hr>
        {{ template "ul.html" }}
        <hr>
        {{ template "ol.html" }}
    </body>
</html>

{{define "ul.html" }}
    <h1>ul.html Demonstration Example</h1>
    <ul>
        <li>Monday</li>
        <li>Tuesday</li>
        <li>Wednesday</li>
        <li>Thursday</li>
        <li>Friday</li>
    </ul>
{{ end  }}
  • ol.html
{{define "ol.html" }}
    <h1>ol.html Demonstration Example</h1>
    <ol>
        <li>Monday</li>
        <li>Tuesday</li>
        <li>Wednesday</li>
        <li>Thursday</li>
        <li>Friday</li>
    </ol>
{{ end  }}
  • Code
package main

import (
"fmt"
"html/template"
"net/http"
)

func EmbedSample(w http.ResponseWriter, r *http.Request) {
 t, err := template.ParseFiles("index.html", "ol.html")
if err != nil {
  fmt.Printf("Error parsing template files: %v", err)
return
 }

 t.Execute(w, nil)
}

func main() {
 server := http.Server{
  Addr:    ":8080",
  Handler: nil,
 }
 http.HandleFunc("/embed", EmbedSample)
 server.ListenAndServe()
}

Leave a Comment