• Part 1
  • Bonuses - sometime later

Overall, this was a good exercise. I was already familiar with the html/template pkg, but I remember how much trouble this package gave me in the past.

This challenge also led me to digging a little deeper into using json.Unmarshal() vs json.NewDecoder().Decode().

Templates

I’ll introduce some of the basics of templating I’ve learned since starting with them.

There is much to learn, but I think if you can get started with getting something on the page, the rest will come with experience and practice.

HTML templates are Go structs (template.Template). They are a type that use the HTML we write in our “template” file, they aren’t the HTML itself.

This confused me at first because I was so used to thinking of a template as the HTML file I was writing.

The file you create to hold your HTML can have any extension you want; Go doesn’t care. Just configure your editor to recognize your chosen file extension as web content or HTML, whatever you need for your editor.

I believe the *.gohtml extension is commonly used, so I use that, and I set my emacs editor to web-mode so I get proper syntax highlighting, etc.

Create the following file and test out the example below:

yourcode/templates/sometemplate.gohtml

1
{{.Message}}

yourcode/main.go

 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"
	"net/http"
)

func main() {

	// parse templates at the top level of our template directory.
	// template.Must() - we must have our templates, so Must() will panic if we can't get them. This is a handy shortcut.
	// template.New() - creates a new "container" of sorts that stores a list of our templates
	// ParseGlob("templates/*.gohtml") will parse any file with the extension .gohtml in the templates directory
	tpls := template.Must(template.New("").ParseGlob("templates/*.gohtml"))

	http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
		// you can view your templates like so:
		for _, tpl := range tpls.Templates() {
			// you'll see that templates are named after the file name
			fmt.Println("Found template", tpl.Name())
		}

		// now we have a list of templates, and we can 'execute' a particular template like so:

		someData := struct{ Message string }{"Some message data"}

		tpls.ExecuteTemplate(w, "sometemplate.gohtml", someData)
	})

	http.ListenAndServe(":8080", nil)
}

How do we handle partials, template hierarchy, etc? That will have to wait for another post. There is a lot to it.

json.Unmarshal vs json.Decoder

Rules of thumb:

  • Use json.Decoder if your data is coming from an io.Reader stream, or you need to decode multiple values from a stream of data.
  • Use json.Unmarshal if you already have the JSON data in memory.

– taken from: Stack Overflow.

If I have this correct, in this case I had already read the entire JSON file into memory with ioutil.ReadFile() and I wasn’t concerned with having to decode multiple requests with JSON, so using json.Unmarshal() was acceptable.

In other words, I was reading a single JSON file, and then I was done.

There was no need to parse something different on each http request causing concern with repeatedly allocating and releasing memory, which json.Decoder handles better.

json.Decoder - HTTP Request w/ JSON

For the sake of comparison, here is a typical use of json.Decoder in decoding JSON from an HTTP POST request body:

 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
33
34
package main

import (
    "net/http"
    "encoding/json"
)

type SomeRequest struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

func main() {

    // assuming a POST request to this route.
    http.HandleFunc("/some-resource", w http.ResponseWriter, r *http.Request) {

        var request SomeRequest

        // r.Body implements the io.Reader interface
        if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
            http.Error(w, "Error", http.StatusInternalServerError)

            return
        }

        // ... your 'request' struct now has the 
        // Username and Password data from the request

    }

    http.ListenAndServe(":8080", nil)
}

json.Decoder - HTTP Request w/ Form Data

It’s worth noting that there is no need to decode or unmarshal the request data coming from a form:

 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
package main

import (
    "net/http"
)

func main() {

    // assuming a POST request to this route.
    http.HandleFunc("/some-resource", w http.ResponseWriter, r *http.Request) {

        // Must call this before attempting to get the form data
        r.ParseForm()

        // PostForm.Get() retrieves data from the FORM only
        // PostForm is of type https://golang.org/pkg/net/url/#Values
        username := r.PostForm.Get("username")
        password := r.PostForm.Get("password")

        // do something
    }

    http.ListenAndServe(":8080", nil)
}