1 of 43

2 of 43

Crash course on Go templates

A quick talk on everything you need to know

Link to slides ➡

3 of 43

Background Info

- My name is Bok Woon, I am a developer @ Teckwah

- Teckwah is a packaging, printing and logistics company (mainly warehousing)

- Not really Go related, I use Java at work

- The contents for this talk came after I tried writing my own blog from scratch

- Had to learn about html/template in-depth

4 of 43

Content Overview

Part 0 - Introduction to Go templates

Part 1 - Template composition

Part 2 - Syntax and variables

Part 3 - Functions, methods and pipes

5 of 43

Part 0: Introduction to Go templates

6 of 43

What are templates?

- Most commonly used to output HTML (i.e. web pages).

- There are other uses, but I'll focus mainly on HTML.

example output:

<!DOCTYPE html>

<html lang="en">

<head>

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Hello</title>

</head>

<body>

<h1>Hello World!</h1>

</body>

</html>

7 of 43

A Hello World example

hello.html

{{ template "header" }}

hello world! my name is {{ .name }}.

{{ template "footer" }}

common.html

{{ define "header" }}

<header>

<p><a href="/">Home</a></p>

<p><a href="/about">About Me</a></p>

<p><a href="/contact">Contact</a></p>

</header>

{{ end }}

{{ define "footer" }}

<p>

Copyright 2021. All rights Reserved.

</p>

{{ end }}

8 of 43

A Hello World example

main.go

t, _ := template.ParseFiles(

"hello.html",

"common.html",

)

t.Execute(os.Stdout, map[string]string{

"name": "bokwoon",

})

output

<header>

<p><a href="/">Home</a></p>

<p><a href="/about">About Me</a></p>

<p><a href="/contact">Contact</a></p>

</header>

hello world! my name is bokwoon.

<p>

Copyright 2021. All rights Reserved.

</p>

9 of 43

Go's templating system is not like the others

- You may have experience with some other templating engines e.g. django templates, jinja2, laravel blade, etc

- Their template references are static: you include some other template by referencing its file path relative to some directory.

e.g. {% include "partials/header.html" %}

- But in Go, template references are dynamic and you must compose the templates yourself.

e.g. {{ template "header" }}

references a specific template relative to the template dir

which template is "header"�referencing? who knows. depends on what templates you composed together

10 of 43

Part 1: Template Composition

an under-documented aspect of Go templates

11 of 43

btw, if you have any questions just post them in the chat and I'll answer them at the end

12 of 43

In Go, a template is essentially a map[string]*Template

When you compose templates together, you are populating this map[string]*Template data structure

The big idea

13 of 43

In Go, a template is essentially a map[string]*Template

hello.html

<p>Hello World</p>

{{ template "footer" }}

{{ define "footer" }}

<p>

<div>my blog</div>

{{ template "copyright" }}

</p>

{{ end }}

{{ define "copyright" }}

Copyright 2021. All rights reserved.

{{ end }}

map[string]*Template{

"hello.html": &Template{

name: "hello.html",

Tree: /* template body */,

},

"footer": &Template{

name: "footer",

Tree: /* template body */,

},

"copyright": &Template{

name: "copyright",

Tree: /* template body */,

},

}

current template

When you execute {{ template "footer" }}, you are doing a map[string]*Template lookup for "footer"

ParseFiles(

"hello.html",

)

14 of 43

Hello World example, translated to map[string]*Template

hello.html

{{ template "header" }}

hello world! my name is {{ .name }}.

{{ template "footer" }}

common.html

{{ define "header" }}

<header>

<p><a href="/">Home</a></p>

<p><a href="/about">About Me</a></p>

<p><a href="/contact">Contact</a></p>

</header>

{{ end }}

{{ define "footer" }}

<p>

Copyright 2021. All rights Reserved.

</p>

{{ end }}

map[string]*Template{

"hello.html": &Template{

name: "hello.html",

Tree: /* template body */,

},

"common.html": &Template{

name: "common.html",

Tree: /* template body */,

},

"header": &Template{

name: "header",

Tree: /* template body */,

},

"footer": &Template{

name: "footer",

Tree: /* template body */,

},

}

current template

ParseFiles(

"hello.html",

"common.html",

)

15 of 43

A template is essentially a map[string]*Template where one of the templates is marked as the current template

// Get the name of the current template

t.Name() == "hello.html"

// execute the current template

t.Execute(os.Stdout, data)

// execute the "header" template

t.ExecuteTemplate(os.Stdout, "header", data)

// execute the "footer" template

t.ExecuteTemplate(os.Stdout, "footer", data)

// iterate through the templates

for i, t := range t.Templates() { ... }

// get the defined templates (in a string)

fmt.Printf("%s\n", t.DefinedTemplates())

// Set the current template to "header"

t = t.Lookup("header")

// and execute it

t.Execute(os.Stdout, data)

t.Name() == "header"

// If you lookup a nonexistent template,

// you get nil

t.Lookup("jdalsdjlkadaf") == nil

map[string]*Template{

"hello.html": &Template{

name: "hello.html",

Tree: /* template body */,

},

"common.html": &Template{

name: "hello.html",

Tree: /* template body */,

},

"header": &Template{

name: "header",

Tree: /* template body */,

},

"footer": &Template{

name: "footer",

Tree: /* template body */,

},

}

current template

16 of 43

Does Go support nested templates?

- No. Even though a {{ define "header" }} is a nested template definition within a template, all templates are hoisted to the top level i.e. they all exist as equals inside a map[string]*Template

hello.html

{{ template "header" }}

hello world! my name is {{ .name }}.

{{ template "footer" }}

common.html

{{ define "header" }}

<header>

<p><a href="/">Home</a></p>

<p><a href="/about">About Me</a></p>

<p><a href="/contact">Contact</a></p>

</header>

{{ end }}

{{ define "footer" }}

<p>

Copyright 2021. All rights Reserved.

</p>

{{ end }}

main.go

t, _ := template.ParseFiles(

"hello.html",

"common.html",

)

main.go

t, _ := template.New("hello.html").Parse(`

{{ template "header" }}

hello world! my name is {{ .name }}.

{{ template "footer" }}

`)

_, _ = t.New("common.html").Parse("\n")

_, _ = t.New("header").Parse(`

<header>

<p><a href="/">Home</a></p>

<p><a href="/about">About Me</a></p>

<p><a href="/contact">Contact</a></p>

</header>

`)

_, _ = t.New("footer").Parse(`

<p>

Copyright 2021. All rights Reserved.

</p>

`)

equivalent to

17 of 43

Do Go templates support template inheritance?

Kind of. It's using plain old template composition.

18 of 43

Why is template inheritance used?

- Pages often have a similar HTML structure and the only thing that changes is the main content.

- The problem is, "include" can only ever point to a specific template in the filesystem.

- It can't resolve to a different template depending on the context. It's a static reference.

/blog/posts

<header>

<p><a href="/">Home</a></p>

<p><a href="/about">About Me</a></p>

<p><a href="/contact">Contact</a></p>

</header>

{% include "index.html" %}

<p>

Copyright 2021. All rights Reserved.

</p>

/blog/posts/about-me

<header>

<p><a href="/">Home</a></p>

<p><a href="/about">About Me</a></p>

<p><a href="/contact">Contact</a></p>

</header>

{% include "post.html" %}

<p>

Copyright 2021. All rights Reserved.

</p>

jinja2 example

19 of 43

How does template inheritance solve it?

- Using template inheritance, you define a parent template then let child templates override specific template blocks in it.

- Looking at base.html, what is the "content" block going to render? Who knows. Depends on whether you invoke index.html or post.html or base.html.

base.html

<header>

<p><a href="/">Home</a></p>

<p><a href="/about">About Me</a></p>

<p><a href="/contact">Contact</a></p>

</header>

{% block "content" %}

this is a placeholder

{% endblock %}

<p>

Copyright 2021. All rights Reserved.

</p>

index.html

{% extends "base.html" %}

{% block "content" %}

Here are my posts

{% endblock %}

post.html

{% extends "base.html" %}

{% block "content" %}

this is a post

{% endblock %}

jinja2 example

20 of 43

Go templates do not need template inheritance because template references are already dynamically resolved

- Looking at base.html, what is {{ template "content" }} going to render? Who knows. Depends on whether you ParseFiles("base.html", "index.html") or ParseFiles("base.html", "post.html") or ParseFiles("base.html")

- If you want a template to render a different thing, you just overwrite its definition in the map[string]*Template. Simple.

base.html

<header>

<p><a href="/">Home</a></p>

<p><a href="/about">About Me</a></p>

<p><a href="/contact">Contact</a></p>

</header>

{{ template "content" }}

{{ define "content" }}

this is a placeholder

{{ end }}

<p>

Copyright 2021. All rights Reserved.

</p>

index.html

{{ define "content" }}

Here are my posts

{{ end }}

post.html

{{ define "content" }}

this is a post

{{ end }}

go templates example

21 of 43

In fact, the "block" keyword essentially does the same thing

- {{ block }} is just syntactic sugar for {{ template }} followed by a {{ define }}.

{{ block "content" . }}

this is a placeholder

{{ end }}

{{ template "content" . }}

{{ define "content" }}

this is a placeholder

{{ end }}

equivalent to

22 of 43

part 1 any questions? about template composition

23 of 43

Part 2: Syntax and variables

24 of 43

Syntax keyword overview

1) {{ define ... }}

2) {{ template ... }}

3) {{ block ... }} {{ end }}

4) {{ if <var> }} {{ else if <var> }} {{ else }} {{ end }}

5) {{ with <var> }} {{ end }}

6) {{ range <var> }} {{ else }} {{ end }}

Anything else (and, or, not, len, index etc) is a function

Already covered in part 1

25 of 43

Template Variables

- Every template has access to exactly one variable, denoted by the dot '.'

- This is whatever variable you passed into Execute()/ExecuteTemplate()

e.g. dot is struct{

Params: Params{

Author: Author{

FirstName: "John",

LastName: "Doe",

},

},

}

{{ . }}

{{ .Params.Author.LastName }}

e.g. dot is map[string]interface{}{

"Params": map[string]interface{}{

"Author": map[string]interface{}{

"FirstName": "John",

"LastName": "Doe",

},

},

}

{{ . }}

{{ .Params.Author.LastName }}

alternatively for maps:

{{ index . "Params" "Author" "LastName" }}

26 of 43

Variables must be passed explicitly down to templates

{{ define "footer" }}

{{ .Params.Author.FirstName }}

{{ end }}

// nothing passed down

{{ template "footer" }} // fails

// dot passed down

{{ template "footer" . }} // passes

// subset of dot passed down

{{ define "footer" }}

{{ .FirstName }}

{{ end }}

{{ template "footer" .Params.Author }}

e.g. dot is map[string]interface{}{

"Params": map[string]interface{}{

"Author": map[string]interface{}{

"FirstName": "John",

"LastName": "Doe",

},

},

}

27 of 43

Variables can be assigned (and reassigned)

{{ define "greet" }}

{{ $firstname := .Params.User.FirstName }} // assignment

<p>Hello, my name is {{ $firstname }}.</p>

{{ $firstname = "Joe" }} // reassignment

<p>Actually, my name is {{ $firstname }}.</p>

{{ end }}

- A variable's scope is extends to the {{ end }} keyword of the current block

- i.e. {{ if }}, {{ with }}, {{ range }}, or the current template

28 of 43

4) {{ if <var> }} {{ else if <var> }} {{ else }} {{ end }}

- basic if-statement; you already know how it works

{{ if .User.IsAuthorized }}

<p>Welcome, {{ .User.Name }}!</p>

{{ else }}

<p>You are not allowed to see this</p>

{{ end }}

- Evaluation is truthy/falsy!

- Falsy values are false, 0, nil, empty array/slice/map/string.

- Anything else is truthy.

Don't do

{{ if gt (len .Users) 0 }}

<p>There are {{ len .Users }} users</p>

{{ end }}

Do ✅

{{ if .Users }}

<p>There are {{ len .Users }} users</p>

{{ end }}

29 of 43

The dot '.' and the dollar '$'

- The dot '.' refers to the current template variable

- {{ . }} // print the variable

- {{ .Params.User.FirstName }} // accessing a nested field

- But the dollar '$' also refers to the current template variable

- {{ $ }} // print the variable

- {{ $.Params.User.FirstName }} // accessing a nested field

- The difference is the dot '.' changes value depending on the context

- It starts off pointing at the top level template variable (same as '$')

- The dot changes inside {{ with }} and {{ range }} blocks (only these 2 cases)

- The dollar '$' always points to the top level template variable. It never changes.

30 of 43

5) {{ with <var> }} {{ end }}

- Behaves like an {{ if }}: if <var> is falsy, the block is not executed.

- Otherwise, the dot '.' is set to <var> inside the {{ with }} block.

- If you want to access anything outside of the dot '.', use '$'

{{ with .User.FirstName }}

<p>FirstName: {{ . }}</p>

<p>LastName: {{ $.User.LastName }}</p>

{{ end }}

// Alternative with explicit variable.

{{ with $firstname := .User.FirstName }}

<p>FirstName: {{ . }}</p>

<p>FirstName: {{ $firstname }}</p>

<p>LastName: {{ $.User.LastName }}</p>

{{ end }}

template variable:

{

"User": {

"FirstName": "John",

"LastName": "Doe"

}

}

31 of 43

6) {{ range <var> }} {{ else }} {{ end }}

- Loops over <var>. <var> must be an array, slice, map or channel.

- If there are 0 elements, the {{ else }} block is executed instead.

- The dot '.' is changed to each successive element of the array/slice/map/channel within the {{ range }} block.

- If you want to access anything outside of the dot '.', use '$'

32 of 43

6) {{ range <var> }} {{ else }} {{ end }}

// Basic example

{{ range .Users }}

<p>User: {{ . }}</p>

<p>FirstName: {{ .FirstName }}</p>

<p>Today is {{ $.Today }}</p>

{{ end }}

template variable:

{

"Users": [

{

"FirstName": "John",

"LastName": "Doe"

},

{

"FirstName": "Jane",

"LastName": "Doe"

}

],

"Today": "Thursday"

}

33 of 43

6) {{ range <var> }} {{ else }} {{ end }}

// Alternative with explicit variable.

{{ range $user := .Users }}

<p>User: {{ . }}</p>

<p>User: {{ $user }}</p>

<p>FirstName: {{ .FirstName }}</p>

<p>FirstName: {{ $user.FirstName }}</p>

<p>Today is {{ $.Today }}</p>

{{ end }}

template variable:

{

"Users": [

{

"FirstName": "John",

"LastName": "Doe"

},

{

"FirstName": "Jane",

"LastName": "Doe"

}

],

"Today": "Thursday"

}

34 of 43

6) {{ range <var> }} {{ else }} {{ end }}

// Alternative with $index, $element

// variables. Applies to slices/arrays.

{{ range $i, $user := .Users }}

<p>Index: {{ $i }}</p>

<p>User: {{ . }}</p>

<p>User: {{ $user }}</p>

<p>FirstName: {{ .FirstName }}</p>

<p>FirstName: {{ $user.FirstName }}</p>

<p>Today is {{ $.Today }}</p>

{{ end }}

template variable:

{

"Users": [

{

"FirstName": "John",

"LastName": "Doe"

},

{

"FirstName": "Jane",

"LastName": "Doe"

}

],

"Today": "Thursday"

}

35 of 43

6) {{ range <var> }} {{ else }} {{ end }}

// Alternative with $key, $value

// variables. Applies to maps.

{{ range $key, $value := .User }}

<p>Key: {{ $key }}</p>

<p>Value: {{ . }}</p>

<p>Value: {{ $value }}</p>

<p>Today is {{ $.Today }}</p>

{{ end }}

template variable:

{

"User": {

"FirstName": "John",

"LastName": "Doe",

"Age": 32,

"Address": "2 ABC St.",

"Email": "jd@email.com",

"Username": "jdoe",

},

"Today": "Thursday"

}

36 of 43

Part 3: Functions, methods and pipes

37 of 43

Templates already have some default functions e.g.

- and

{{ if and <cond> <cond> <cond> ... }} {{ end }}

- or

{{ if or <cond> <cond> <cond> ... }} {{ end }}

- not

{{ if not <cond> }} {{ end }}

- len

{{ len <var> }}

// <var> is one of string | slice | array | map | channel

- index

{{ index <slice> <num> ... }}

{{ index <map> <key> ... }}

38 of 43

Templates can call user-defined functions

- You can install new functions into a template by calling .Funcs(map[string]interface{})

template.

New("t").

Funcs(map[string]interface{}{

"greet": func(name string) string { return "hello " + name },

}).

Parse(`{{ greet "bob" }}`)

- Functions can take in any number of arguments but must return one result

func() string

func(s string) string

func(args ...interface{}) interface{}

- You can optionally return an error. If the error is non-nil it will halt template execution

func() (string, error)

func(s string) (string, error)

func(args ...interface{}) (interface{}, error)

39 of 43

Templates can call methods

- Calling a method looks like a field access

type User struct { greeting string }

func (u User) Greet(name string) string {

return g.greeting + " " + name

}

t, _ := template.New("t").Parse(`{{ .User.Greet "bob" }}`)

t.Execute(os.Stdout, map[string]interface{}{

"User": User{greeting: "hello"},

})

40 of 43

Templates can't directly call variables that are functions

- If you are trying to call a variable that happens to be a function, you must use 'call'

type User struct {

Greet func(string) string

}

t, _ := template.New("t").Parse(`{{ call .User.Greet "bob" }}`)

t.Execute(os.Stdout, map[string]interface{}{

"User": User{

Greet: func(name string) { return "hello " + name },

},

})

41 of 43

The results of one function can be piped into another

- When piped, the result of a function is passed in as the last argument to the next function

t, _ := template.

New("t").

Funcs(map[string]interface{}{

"upper": strings.ToUpper,

"greet": func(greeting, name string) string {

return greeting + " " + name

},

}).

Parse(`{{ upper . | greet "hello" }}`) // "hello BOB"

t.Execute(os.Stdout, "bob")

42 of 43

The End

More stuff I didn't cover:

- Templates can be parsed separately and then combined together.

- It's just merging two map[string]*Template-s.

- If two templates combine together, how do their functions interact with each other? Which function wins?

- The functions are just map[string]interface{}-s. They also get merged together.

- html/template will automatically secure HTML/CSS/JS/URL input for you. [1][2]

- even the upcoming {{ break }} and {{ continue }} keywords will respect this. [3]

- Functions must be defined before Parse(). But then you are free to overwrite them after that (why??)

43 of 43

text/template: make and/or operators short-circuit evaluation�https://github.com/golang/go/issues/31103

proposal accepted and CL committed

text/template: add support for optional chaining�https://github.com/golang/go/issues/43608

proposal seems dormant, but optional chaining will be so valuable to have