Pongo with Echo or net/http
How to use pongo with the echo framework or net/http
January 15, 2017
It’s been a while since I started web development with Go, and I’ve been
enjoying it a lot. I’ve used mainly Echo and
the well-known net/http
library.
However, today I am not here to talk about web frameworks, but about a
templating engine instead. I like the default html/template
package, but I am
not very fond of it when it comes to bigger projects. Some things are simply
harder to accomplish (like template inheritance) than I am used to.
Aside from that, on certain projects I work with front-end developers who are rarely familiar with the syntax of the default templating library. And I’ve heard more than once that the documentation regarding this library is hard to digest. I can understand why, if you’re not accustomed to Go or it’s documentation.
However, there is an alternative, which both I and most of the people I work with very much prefer, it’s called pongo2. It is based on Django-template syntax, but nowadays a lot of programming languages have libraries supporting this syntax:
- Jinja (Python) - http://jinja.pocoo.org/
- Twig (PHP) - http://twig.sensiolabs.org/
- JTwig (Java) - http://jtwig.org/
- DotLiquid (.NET) - http://dotliquidmarkup.org/
- Nunjucks (NodeJS) - http://mozilla.github.io/nunjucks/
So let’s get started, shall we?
First of all, install pongo2: go get -u github.com/flosch/pongo2
The most important method to start rendering from files is pongo2.FromFile
. It
has expects one argument: a string
, the name of the file you want to render.
It returns a *pongo2.Template
and an error
. On this *pongo2.Template
we
call ExecuteWriter
and pass it a pongo2.Context
, which is the data your
template will be using, and an io.Writer
.
Throughout my examples I will use this simple template:
<p>My name: {{ name }}</p>
<p>The answer: {{ answer() }}</p>
<p>
Items:
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</p>
The result will always be:
<p>My name: Machiel</p>
<p>The answer: 42</p>
<p>
Items:
<li>Apple</li>
<li>Pear</li>
<li>Banana</li>
<li>Pineapple</li>
</p>
A simple example using net/http
Assume the following code, using a simple HTTP server that listens on :1333
:
package main
import (
"log"
"net/http"
"github.com/flosch/pongo2"
)
func main() {
http.HandleFunc("/", Index)
http.ListenAndServe(":1333", nil)
}
func Index(w http.ResponseWriter, r *http.Request) {
t, err := pongo2.FromFile("index.html")
if err != nil {
log.Printf("could not render: %s", err)
return
}
data := pongo2.Context{
"name": "Machiel",
"answer": func() int {
return 42
},
"items": []string{
"Apple", "Pear", "Banana", "Pineapple",
},
}
if err := t.ExecuteWriter(data, w); err != nil {
log.Printf("could not execute: %s", err)
}
}
The only place where something actually happens is the Index
function. First
we try to load the template from a file, next up we build our context
(pongo2.Context
). Under the hood it is nothing more than a
map[string]interface{}
, so you can pass in pretty much whatever you like:
structs, interfaces, functions, you name it.
After that we use the ExecuteWriter
function to write to the
http.ResponseWriter
. And that’s it, you’ve just used pongo2.
Better example
I generally like to declare a Renderer
struct which handles the rendering for
me, just passing the required dependencies at that moment. Then you just pass
the Renderer
to your handler functions, and call the Render
function with
the desired parameters, as shown in this example:
package main
import (
"io"
"log"
"net/http"
"github.com/flosch/pongo2"
)
type Renderer struct {
Debug bool
}
func (r Renderer) Render(w io.Writer, name string, data pongo2.Context) error {
var t *pongo2.Template
var err error
if r.Debug {
t, err = pongo2.FromFile(name)
} else {
t, err = pongo2.FromCache(name)
}
// Add some static values
data["version_number"] = "v0.0.1-beta"
if err != nil {
return err
}
return t.ExecuteWriter(data, w)
}
func main() {
renderer := Renderer{
Debug: false,
}
http.HandleFunc("/", Index(&renderer))
http.ListenAndServe(":1333", nil)
}
func Index(renderer *Renderer) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data := pongo2.Context{
"name": "Machiel",
"answer": func() int {
return 42
},
"items": []string{
"Apple", "Pear", "Banana", "Pineapple",
},
}
if err := renderer.Render(w, "index.html", data); err != nil {
log.Printf("could not execute: %s", err)
}
}
}
This is a very trivial example, but you could do crazy here and pass things like
the base directory for your templates to the Renderer
.
Example with echo
As for echo, the code looks quite the same. At the time of writing I am using
echo v3, the way of using a Renderer
in previous versions of echo is slightly
different (as in, one or two lines of code). If you’re unable to
figure out how to use it though, let me know.
package main
import (
"errors"
"io"
"net/http"
"github.com/labstack/echo"
"github.com/flosch/pongo2"
)
type Renderer struct {
Debug bool
}
func (r Renderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
var ctx pongo2.Context
if data != nil {
var ok bool
ctx, ok = data.(pongo2.Context)
if !ok {
return errors.New("no pongo2.Context data was passed...")
}
}
var t *pongo2.Template
var err error
if r.Debug {
t, err = pongo2.FromFile(name)
} else {
t, err = pongo2.FromCache(name)
}
// Add some static values
ctx["version_number"] = "v0.0.1-beta"
if err != nil {
return err
}
return t.ExecuteWriter(ctx, w)
}
func main() {
renderer := Renderer{
Debug: false,
}
e := echo.New()
e.Renderer = renderer
e.GET("/", Index)
e.Start(":1313")
}
func Index(c echo.Context) error {
data := pongo2.Context{
"name": "Machiel",
"answer": func() int {
return 42
},
"items": []string{
"Apple", "Pear", "Banana", "Pineapple",
},
}
return c.Render(http.StatusOK, "index.html", data)
}
I hope this was a good (albeit short) introduction to pongo2, but it should at least be enough to get up and running. I would say, give it a shot :).