Go pongo2
last modified April 11, 2024
In this article we show how to work with templates in Golang with pongo2 template engine.
$ go version go version go1.22.2 linux/amd64
We use Go version 1.22.2.
A template engine is a library designed to combine templates with a data to produce documents. Template engines are used to generate large amounts of emails, in source code preprocessing, or to produce dynamic HTML pages.
A template consists of static data and dynamic regions. The dynamic regions are later replaced with data. The rendering function later combines the templates with data. A template engine is used to combine templates with a data model to produce documents.
The pongo2 library is a Go template engine inspired by Django's template engine.
The pongo2 uses various delimiters in template string:
{% %}
- statements{{ }}
- expressions to print to the template output{# #}
- comments which are not included in the template output# ##
- line statements
Templates can be read from strings with pongo2.FromString
, files
with pongo2.FromFile
, or bytes with pongo2.FromBytes
.
The documents are rendered with Execute
,
ExecuteWriter
, or ExecuteBytes
functions. These
functions accept a Context
, which provides constants, variables,
instances or functions to a template.
Go pongo2.FromString
The pongo2.FromString
reads a template from a string.
package main import ( "fmt" "log" "github.com/flosch/pongo2/v5" ) func main() { tpl, err := pongo2.FromString("Hello {{ name }}!") if err != nil { log.Fatal(err) } res, err := tpl.Execute(pongo2.Context{"name": "John Doe"}) if err != nil { log.Fatal(err) } fmt.Println(res) }
The example produces a simple text message.
tpl, err := pongo2.FromString("Hello {{ name }}!")
The variable to print is placed within the {{ }}
brackets.
res, err := tpl.Execute(pongo2.Context{"name": "John Doe"})
We render the final string with Execute
. In the context, we pass
a value for the name
variable.
$ go run main.go Hello John Doe!
package main import ( "fmt" "log" "github.com/flosch/pongo2/v5" ) func main() { tpl, err := pongo2.FromString("{{ name }} is a {{ occupation }}") if err != nil { log.Fatal(err) } name, occupation := "John Doe", "gardener" ctx := pongo2.Context{"name": name, "occupation": occupation} res, err := tpl.Execute(ctx) if err != nil { log.Fatal(err) } fmt.Println(res) }
In this example, we pass two variables in the context.
$ go run main.go John Doe is a gardener
Go pongo2.FromFile
With the pongo2.FromFile
function, we read the template from a
file.
{{ name }} is a {{ occupation }}
This is the template file.
package main import ( "fmt" "log" "github.com/flosch/pongo2/v5" ) func main() { tpl, err := pongo2.FromFile("message.tpl") if err != nil { log.Fatal(err) } name, occupation := "John Doe", "gardener" ctx := pongo2.Context{"name": name, "occupation": occupation} res, err := tpl.Execute(ctx) if err != nil { log.Fatal(err) } fmt.Println(res) }
The example produces a simple message, while reading the template from a file.
Go pongo2 for directive
The for
directive is used to iterate over a data collection in a
template.
{% for word in words -%} {{ word }} {% endfor %}
In the template, we use the for
directive to go through the
elements of the words
data structure. The - character strips
whitespace characters.
package main import ( "fmt" "log" "github.com/flosch/pongo2/v5" ) func main() { tpl, err := pongo2.FromFile("words.tpl") if err != nil { log.Fatal(err) } words := []string{"sky", "blue", "storm", "nice", "barrack", "stone"} ctx := pongo2.Context{"words": words} res, err := tpl.Execute(ctx) if err != nil { log.Fatal(err) } fmt.Println(res) }
In the program, we pass a slice of words to the tempate engine. We get a list of words as the output.
$ go run main.go sky blue storm nice barrack stone
Go pongo2 filter
A filter can be applied to data to modify them. Filters are applied after the | character.
{% for word in words -%} {{ word }} has {{ word | length }} characters {% endfor %}
The length
filter returns the size of the string.
package main import ( "fmt" "log" "github.com/flosch/pongo2/v5" ) func main() { tpl, err := pongo2.FromFile("words.tpl") if err != nil { log.Fatal(err) } words := []string{"sky", "blue", "storm", "nice", "barrack", "stone"} ctx := pongo2.Context{"words": words} res, err := tpl.Execute(ctx) if err != nil { log.Fatal(err) } fmt.Println(res) }
In the program, we pass a slice of words to the template. We print each word and its size.
$ go run main.go sky has 3 characters blue has 4 characters storm has 5 characters nice has 4 characters barrack has 7 characters stone has 5 characters
Go pongo2 if condition
Conditions can be created with if/endif
directives.
{% for todo in todos -%} {% if todo.Done %} {{- todo.Title -}} {% endif %} {% endfor %}
In the template file, we use the if directive to output only tasks that are finished.
package main import ( "fmt" "log" "github.com/flosch/pongo2/v5" ) type Todo struct { Title string Done bool } type Data struct { Todos []Todo } func main() { tpl, err := pongo2.FromFile("todos.tpl") if err != nil { log.Fatal(err) } todos := []Todo{ {Title: "Task 1", Done: false}, {Title: "Task 2", Done: true}, {Title: "Task 3", Done: true}, {Title: "Task 4", Done: false}, {Title: "Task 5", Done: true}, } ctx := pongo2.Context{"todos": todos} res, err := tpl.Execute(ctx) if err != nil { log.Fatal(err) } fmt.Println(res) }
We generate on output from a slice of todos. In the output we include only finished tasks.
Server example
In the next example, we use templates in a server application.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Users</title> </head> <body> <table> <thead> <tr> <th>Name</th> <th>Occupation</th> </tr> </thead> <tbody> {% for user in users %} <tr> <td>{{ user.Name }} </td> <td>{{ user.Occupation }}</td> </tr> {% endfor %} </tbody> </table> </body> </html>
The output is an HTML file. The users are displayed in an HTML table.
package main import ( "net/http" "github.com/flosch/pongo2/v5" ) type User struct { Name string Occupation string } var tpl = pongo2.Must(pongo2.FromFile("users.html")) func usersHandler(w http.ResponseWriter, r *http.Request) { users := []User{ {Name: "John Doe", Occupation: "gardener"}, {Name: "Roger Roe", Occupation: "driver"}, {Name: "Peter Smith", Occupation: "teacher"}, } err := tpl.ExecuteWriter(pongo2.Context{"users": users}, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func main() { http.HandleFunc("/users", usersHandler) http.ListenAndServe(":8080", nil) }
The web server returns an HTML page with a table of users for the
/users
URL path.
var tpl = pongo2.Must(pongo2.FromFile("index.html"))
The pongo2.Must
is a helper function which pre-compiles the
templates at application startup.
err := tpl.ExecuteWriter(pongo2.Context{"users": users}, w)
The ExecuteWriter
renders the template with the given context and
writes the output to the response writer on success. Nothing is written on
error; instead the error is being returned.
Source
In this article we have created dynamic documents using third-party pongo2 templating engine.
Author
List all Go tutorials.