Rendering Dynamic Data in Go's http template

2016-02-07 by Adler Hsieh


In the last post we built a basic file server using the most basic functionality of Go's http package. In this post we make it a little further by rendering dynamic data using the same package. It means we're going to use a basic HTML page and replace several parts with variables, which will be rendered with data given in the Go program.

The First Step

We start from an empty http server that renders nothing, and build the template step by step.

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        // Let's start here
    })
    http.ListenAndServe(":8000", nil)
}

Rendering data requires text/template package, so that's import it by adding another package below the net/http:

import (
    "net/http"
    "text/template"
)

And we declare another constant to store the HTML content we're rendering:

const doc = `
<!DOCTYPE html>
<html>
    <head>
        <title>My fruits</title>
    </head>
    <body>
        <h3>Hi, the fruits are:</h3>
        <ul>
            <li>Apple</li>
            <li>Lemon</li>
            <li>Orange</li>
        </ul>
    </body>
</html>
`

The View Syntax

Go offers its DSL in text/template package which we can refer to its documentation. Accordingly we replace the title of the HTML content with the varialbe Title like:

{{.Title}}

The leading dot looks a little confusing, but it means self in this context. Since every template is populated with a specific variable or struct, it is necessary to point out the self in every context. In this case, we can also use range keyword to loop through objects like:

{{range .Fruit}}
    <li>{{.}}</li>
{{end}}

Also, it is possible to execute if statement in view:

{{if eq . "Fruits"}}
    <title>My Fruits</title>
{{else}}
    <title>{{.}}</title>
{{end}}

The eq keyword compares the two arguments, which is weird at first glance since in Go we use == normally for comparison. In fact I don't know why but the keywords are a little limited and inconvinient to use.

Populating Data

Knowing the syntax, we set the data using a struct:

type Context struct {
    Title string
    Name  string
    Fruit [3]string
}

And replace the corresponding variables in the HTML:

const doc = `
<!DOCTYPE html>
<html>
    <head>
        {{.Title}}
    </head>
    <body>
        <h3>Hi, {{.Name}}. The fruits are:</h3>
        <ul>
            {{range .Fruit}}
                <li>{{.}}</li>
            {{end}}
        </ul>
    </body>
</html>
`

Finally we render the data in http.HandleFunc function like:

package main

import (
    "net/http"
    "text/template"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        w.Header().Add("Content Type", "text/html")
        // The template name "template" does not matter here
        templates := template.New("template")
        // "doc" is the constant that holds the HTML content
        templates.New("doc").Parse(doc)
        context := Context{
            Title: "My Fruits",
            Name: "John",
            Fruits: [3]string{"Apple", "Lemon", "Orange"},
        }
        templates.Lookup("doc").Execute(w, context)
    })
    http.ListenAndServe(":8000", nil)
}

// The const and struct we declared are here

Nice and clean. Run the file and visit localhost:8000. The HTML content should be rendered properly.