Learning Golang - templates

When I started learning about Golang templates much of what I read was around their syntax and feature set. It didn’t take long, but I found myself struggling to do things that struck me as being pretty basic. So I figured I’d write down a few things I’ve learned.

By no means is this intended to be a proper (or even correct) howto on Golang templates, rather it’s just what I’ve learned so far. Here’s what I was trying to accomplish:

  1. Have a directory of templates (header.html, foobar.html, etc).
  2. Have a directory of static files (css, images, etc).
  3. Use some templates as full pages (about.html, hello.html).
  4. Use some templates as partials (header.html, footer.html).
  5. Serve static content in a manner similar to http.FileServer.
  6. Exclude templates from the static files being served.
  7. Support custom template functions.
  8. Compile everything into a single static binary (including templates and static files).

What I used

Here’s a quick punch list of the things I used and what they are for.

Tool Purpose
Golang 1.5 Compiler
go generate Precompiler
github.com/jteeuwen/go-bindata Code generation
github.com/elazarl/go-bindata-assetfs http.Dir for use with http.FileServer
github.com/julienschmidt/httprouter Mux Handler

Installation

go get github.com/jteeuwen/go-bindata/...
go get github.com/elazarl/go-bindata-assetfs/...
go get github.com/julienschmidt/httprouter

Directory structure

The goal was to wind up with something like this:

.
├── functions.go
├── main.go
├── templates
│   ├── footer.html
│   ├── header.html
│   └── hello.html
└── static
    └── golang.png

Inside the static folder I’d put static assets, things like pictures, css, javascript. Inside templates I’d put the full and partial templates, and template functions would go inside functions.go. The application itself would then be main.go.

Functions.go

A hello world example might look like this, with a single function to uppercase something:

package main

import (
	"strings"
	"html/template"
)

var (
	templateMap = template.FuncMap{
		"Upper": func(s string) string {
			return strings.ToUpper(s)
		},
	}
)

Templates

templates/header.html

<html>
  <head>
    <title>{{ .Title }}</title>
  </head>
  <body>

templates/hello.html

{{ template "templates/header.html" . }}

Hello world! My name is {{ .Name | Upper }}

{{ template "templates/footer.html" . }}

templates/footer.html

  </body>
</html>

Sample hello world application

main.go:

package main

//go:generate go-bindata-assetfs static/... templates/...

import (
	"html/template"
	"log"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

// Model of stuff to render a page
type Model struct {
	Title string
	Name  string
}

// Templates with functions available to them
var templates = template.New("").Funcs(templateMap)

// Parse all of the bindata templates
func init() {
	for _, path := range AssetNames() {
		bytes, err := Asset(path)
		if err != nil {
			log.Panicf("Unable to parse: path=%s, err=%s", path, err)
		}
		templates.New(path).Parse(string(bytes))
	}
}

// Render a template given a model
func renderTemplate(w http.ResponseWriter, tmpl string, p interface{}) {
	err := templates.ExecuteTemplate(w, tmpl, p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

// Well hello there
func hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	model := Model{Name: ps.ByName("name")}
	renderTemplate(w, "templates/hello.html", &model)
}

// The server itself
func main() {
	// mux handler
	router := httprouter.New()

	// Example route that takes one rest style option
	router.GET("/hello/:name", hello)

	// Serve static assets via the "static" directory
	router.ServeFiles("/static/*filepath", assetFS())

	// Serve this program forever
	log.Fatal(http.ListenAndServe(":8080", router))
}

Build the application

go generate
go build && ./golang-templates-example

Then you can use something to test it:

$ curl -s localhost:8080/hello/jack
<html>
  <head>
    <title></title>
  </head>
  <body>


Hello world! My name is JACK

  </body>
</html>

Overview

go-bindata does a great job of compiling static assets into a single binary, and go-bindata-assetfs makes it easy to serve those assets over http. I wasn’t able to find an easy way to expose ExecuteTemplate in a manner that made it possible to add a function map, but a quick for loop in init() seems to do the trick. Partials are managed here purely using upstream documentation. I have not yet sorted out how to exclude the templates from being served over http (hence the strike through). A working example if this sample project is on Github:

https://github.com/jmcfarlane/golang-templates-example

For those of you that actually know how to do this stuff, I’d love to hear from you (via pull request or whatever).

Cheers!

Updates

  1. Switched from text/template to html/template, thanks strothjs.
  2. Removed templates from static content served, GH2.