Building Web Server with Go - Part 5

· ☕ 8 min read
This is part 5 of 15 part series on building web server with go.
Checkout https://www.gophersumit.com/series/web-server/ for more.

html templates

template is what it sounds like. It is a predefined html with placeholders to render data. This template is not directly returned to user, it is first parsed by a component called template engine. The work of template engine involves parsing the html and replacing the placeholders with actual data that is provided to template engine.

In previous post we had returned html form to user. In this post, we will understand how templates are parsed by Go and and how we can pass data to templates.

template libraries

Go comes with 2 template libraries

For web applications, we use html/template which in turn uses text/template for parsing data internally. Before we use html/template, it is necessary to understand how text/template works.

text/template

For working with text/template, there are 3 steps

  1. Create a new named template
  2. Define some text with placeholders and set this text for template created in step 1.
  3. When we want to use this template, Execute this template by passing io.Writer and data.

Let's see this in action

  • On line 8, we have defined our struct type which will have data.
1
2
3
4
5
type Information struct {
	Username  string
	Useremail string
	Seat      int
}
  • On line 15, we define our template with placeholders. {{.}} is the way to refers to data that will be passed to template when template is executed.
  • In this case we know that we will be passing a struct of type Information.
  • . points to data being passed, and in this case since this is of type struct, we can access struct fields in our template.
Struct FieldTemplate
Username{{.Username}}
Useremail{{.Useremail}}
Seat{{.Seat}}
  • on line 21, we create a new template named “information” and set its template to texttemplate
    infoTemplate, err := template.New("information").Parse(texttemplate)

  • line 25 executes the template we have created by passing our struct value to it.
    infoTemplate.Execute(os.Stdout, info)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
	"os"
	"text/template"
)

type Information struct {
	Username  string
	Useremail string
	Seat      int
}

func main() {
	texttemplate := `{{.Username}} is allocated seat {{.Seat}}.
{{.Username}} contacted at {{.Useremail}}\n`
	info := Information{
		Username:  "sumit",
		Useremail: "gophersumit@gmail.com",
		Seat:      21,
	}
	infoTemplate, err := template.New("information").Parse(texttemplate)
	if err != nil {
		panic(err)
	}
	infoTemplate.Execute(os.Stdout, info)
}
// output
// sumit is allocated seat 21.
// sumit can be contacted at gophersumit@gmail.com

Actions

Go has actions which are embedded commands that text/template parser understands and transforms our template accordingly.

conditional and loops

One of the common requirements in any template engine is to have content that is rendered based on some condition similar to if else blocks in code or show repeating content similar to for loops in code. Let's see how this can be done using templates.

if…else

If comes in 2 flavors same as in programming :

{{if condition}} data {{end}}
{{if condition}} data {{else}} other data {{end}}
  1. {{if}} and {{else}} are called actions.
  2. We need to end action scope using {{end}}
  3. It works similar to how if else blocks Go code.

Let's see running example to see this in action.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
	"fmt"
	"html/template"
	"log"
	"os"
)

type Result struct {
	Ok   bool
	Data string
}

func main() {
	templatetext := "{{if .Ok}}Results = {{.Data}} {{else}}No Results found\n {{end}}"
	r1 := Result{Ok: true, Data: "Some data from server\n"}
	r2 := Result{Ok: false, Data: ""}
	conditionalTemplate, err := template.New("conditional").Parse(templatetext)
	if err != nil {
		log.Fatalf("template parse error")
	}
	fmt.Printf("Executing first template r1\n")
	conditionalTemplate.Execute(os.Stdout, r1)
	fmt.Printf("Executing second template r2\n")
	conditionalTemplate.Execute(os.Stdout, r2)

}
//Executing first template r1
//Results = Some data from server
//Executing second template r2
//No Results found

loops

range that we used to loop over array, slice, map, or channel. If the value has length zero, nothing is output; otherwise, dot is set to the successive elements of the array,slice, or map.

{{range .}} Name {{.Name}}, Age {{.Age}} {{end}}

Similar to if...else, we have to end range block with {{end}}.
Interesting thing to notice is that data to which . points to is set to each value in iteration.

Let's see an example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
	"log"
	"os"
	"text/template"
)

type User struct {
	Name string
}

func main() {
	loopText := `{{range .}}Found {{.Name}}{{"\n"}}{{end}}`
	allUsers := []User{
		{Name: "sumit"}, {Name: "deepti"}, {Name: "amit"},
	}
	loopTemplate, err := template.New("loop").Parse(loopText)
	if err != nil {
		log.Fatalf("error parsing template")
	}
	loopTemplate.Execute(os.Stdout, allUsers)
}
//Found sumit
//Found deepti
//Found amit
  1. When {{.range .}} is encountered, . refers to slice []User.
  2. Inside range , . refers to individual User type.

There are many more actions, for a complete list refer to text/http docs https://pkg.go.dev/text/template?tab=doc#pkg-overview
Now that we know basics of template, let's use them with html.

html/template

html/template provider some additional helper methods to work with templates with html pages with security checks.

Package template (html/template) implements data-driven templates for generating HTML output safe against code injection -https://pkg.go.dev/html/template?tab=doc#pkg-overview

Let's build a simple web server to parse html/template

app.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import (
  "net/http"
)

type Message struct{ Data string }

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", sendTemplate)
  http.ListenAndServe(":3000", mux)
}

handler.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

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

func sendTemplate(w http.ResponseWriter, r *http.Request) {
  res, err := template.ParseFiles("home.html")
  if err != nil {
      w.WriteHeader(http.StatusInternalServerError)
      return
  }
  msg := Message{Data: "Gophers"}
  // execute template
  w.Header().Set("Content-type", "text/html")
  err = res.Execute(w, msg)
  if err != nil {
      w.WriteHeader(http.StatusInternalServerError)
      return
  }
}

home.html

1
2
3
  <div>
      Hello, {{.Data}}
  </div>

curl

1
2
3
4
$ curl http://localhost:3000
<div>
  Hello, Gophers
</div>
  1. Our html template is simple <div>Hello, {{.Data}}</div>. It expects a type which should have Data field.
  2. We have create a type with Data field and initialized with some value.
  3. First we parse template file using homeTemplate, err := template.ParseFiles("home.html") code.
  4. Then, we execute the template by passing our data and write this to response writer. homeTemplate.Execute(w, msg)

reusing templates

Go provides a way to reuse template within other templates. A common use case would be to define header and footer and separate template and reuse them in all the pages. We can do this using nested templates.

define template

first , we need define a template. Go has define keyword to define a named template in our html.
header is name of template and we define where header ends by using {{end}}.

{{define "header"}}
<div>
    A Fancy header for site
</div>
{{.HeaderData}}
{{end}}

use template

We update our home template to include this newly created header template as part of home.
Remember to pass . if you wish to pass data to this template. This needs to be passed explicitly.

{{template "header" .}}
<div>
    Hello, {{.HomeData}}
</div>

Let's also update our type to send data for Home and Header

1
type Message struct{ HomeData, HeaderData string }

Let's spin this web server and see it in action

app.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import (
  "net/http"
)

type Message struct{ HomeData, HeaderData string }

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", sendTemplate)
  http.ListenAndServe(":3000", mux)
}

handler.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

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

func sendTemplate(w http.ResponseWriter, r *http.Request) {
  homeTemplate, err := template.ParseFiles("home.html", "header.html")
  if err != nil {
      w.WriteHeader(http.StatusInternalServerError)
      return
  }
  msg := Message{HomeData: "Gophers", HeaderData: "Some header content"}
  // execute template
  w.Header().Set("Content-type", "text/html")
  err = homeTemplate.Execute(w, msg)
  if err != nil {
      w.WriteHeader(http.StatusInternalServerError)
      return
  }
}

header.html

{{define "header"}}
<div>
  A Fancy header for site
</div>
{{.HeaderData}}
{{end}}

home.html

{{template "header" .}}
<div>
  Hello, {{.HomeData}}
</div>

curl

1
2
3
4
5
6
7
8
$ curl http://localhost:3000
<div>
  A Fancy header for site
</div>
Some header content
<div>
  Hello, Gophers
</div>

Spend some time reading text/template and html/template documentation, it has very powerful constructs and will take a little more time to master.


Sumit
WRITTEN BY
Sumit
Gopher


What's on this Page