Rendering Dynamic Data in Go's http template
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.