Displaying dynamic data
func (app *application) snippetView(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil || id < 1 { http.NotFound(w, r) return } snippet, err := app.snippet.Get(id) if err != nil { if errors.Is(err, models.ErrNoRecord) { http.NotFound(w, r) } else { app.serverError(w, r, err) } return } // Initialize a slice containing the paths to the view.html file, // plus the base layout and navigation partial that we made earlier. files := []string{ "./ui/html/base.html", "./ui/html/partials/nav.html", "./ui/html/pages/view.html", } // Parse the template files. ts, err := template.ParseFiles(files...) if err != nil { app.serverError(w, r, err) return } // And then execute them. Notice how we are passing in the snippet // data (a models.Snippet struct) as the final parameter. err = ts.ExecuteTemplate(w, "base", snippet) if err != nil { app.serverError(w, r, err) } }
Any data that you pass as the final parameter to ts.ExecuteTemplate() is represented within your HTML templates by the . character (referred to as dot).
In this specific case, the underlying type of dot will be a models.Snippet struct. When the underlying type of dot is a struct, you can render (or yield) the value of any exported field in your templates by postfixing dot with the field name. So, because our models.Snippet struct has a Title field, we could yield the snippet title by writing {{.Title}} in our templates.
{{define "title"}}Snippet #{{.ID}}{{end}} {{define "main"}} <main> <div class="snippet"> <div class="metadata"> <strong>{{.Title}}</strong> <span>#{{.ID}}</span> </div> <pre><code>{{.Content}}</code></pre> <div class="metadata"> <time>Created: {{.Created}}</time> <time>Expires: {{.Expires}}</time> </div> </div> </main> {{end}}
Rendering multiple pieces of data
An important thing to explain is that Go’s html/template package allows you to pass in one — and only one — item of dynamic data when rendering a template. But in a real-world application there are often multiple pieces of dynamic data that you want to display in the same page.
A lightweight and type-safe way to achieve this is to wrap your dynamic data in a struct which acts like a single ‘holding structure’ for your data.
Let’s create a new cmd/web/templates.go file, containing a templateData struct to do exactly that.
package main import "snippetbox/internal/models" // tempData type acts as the holding structure for any // dynamic data that we want to pass to our HTML templates. // At the moment it only contains one field, but we'll add // more to it as the build progresses. type templateData struct { Snippet models.Snippet }
And then let’s update the snippetView handler to use this new struct when executing our templates:
func (app *application) snippetView(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil || id < 1 { http.NotFound(w, r) return } snippet, err := app.snippet.Get(id) if err != nil { if errors.Is(err, models.ErrNoRecord) { http.NotFound(w, r) } else { app.serverError(w, r, err) } return } files := []string{ "./ui/html/base.html", "./ui/html/partials/nav.html", "./ui/html/pages/view.html", } ts, err := template.ParseFiles(files...) if err != nil { app.serverError(w, r, err) return } // Create an instance of a templateData struct holding the snippet data. data := templateData{ Snippet: snippet, } // Pass in the templateData struct when executing the template. err = ts.ExecuteTemplate(w, "base", data) if err != nil { app.serverError(w, r, err) } }
So now, our snippet data is contained in a models.Snippet struct within a templateData struct. To yield the data, we need to chain the appropriate field names together like so:
{{define "title"}}Snippet #{{.Snippet.ID}}{{end}} {{define "main"}} <main> <div class="snippet"> <div class="metadata"> <strong>{{.Snippet.Title}}</strong> <span>#{{.Snippet.ID}}</span> </div> <pre><code>{{.Snippet.Content}}</code></pre> <div class="metadata"> <time>Created: {{.Snippet.Created}}</time> <time>Expires: {{.Snippet.Expires}}</time> </div> </div> </main> {{end}}
Dynamic content escaping
The html/template package automatically escapes any data that is yielded between {{ }} tags. This behavior is hugely helpful in avoiding cross-site scripting (XSS) attacks, and is the reason that you should use the html/template package instead of the more generic text/template package that Go also provides.
As an example of escaping, if the dynamic data you wanted to yield was:
<span>{{"<script>alert('xss attack')</script>"}}</span>
It would be rendered harmlessly as:
<span><script>alert('xss attack')</script></span>
The html/template package is also smart enough to make escaping context-dependent. It will use the appropriate escape sequences depending on whether the data is rendered in a part of the page that contains HTML, CSS, Javascript or a URI.
Nested templates
It’s really important to note that when you’re invoking one template from another template, dot needs to be explicitly passed or pipelined to the template being invoked. You do this by including it at the end of each {{template}} or {{block}} action, like so:
{{template "main" .}}
{{block "sidebar" .}}{{end}}
As a general rule, my advice is to get into the habit of always pipelining dot whenever you invoke a template with the {{template}} or {{block}} actions, unless you have a good reason not to.
Calling methods
If the type that you’re yielding between {{ }} tags has methods defined against it, you can call these methods (so long as they are exported and they return only a single value — or a single value and an error).
For example, our .Snippet.Created struct field has the underlying type time.Time , meaning that you could render the name of the weekday by calling its Weekday() method like so:
<span>{{.Snippet.Created.Weekday}}</span>
You can also pass parameters to methods. For example, you could use the AddDate() method to add six months to a time like so:
<span>{{.Snippet.Created.AddDate 0 6 0}}</span>
Notice that this is different syntax to calling functions in Go — the parameters (years, months, days) are not surrounded by parentheses and are separated by a single space character, not a comma.
HTML comments
Finally, the html/template package always strips out any HTML comments you include in your templates, including any conditional comments.
The reason for this is to help avoid XSS attacks when rendering dynamic content. Allowing conditional comments would mean that Go isn’t always able to anticipate how a browser will interpret the markup in a page, and therefore it wouldn’t necessarily be able to escape everything appropriately. To solve this, Go simply strips out all HTML comments.
Template actions and functions
We’ve already talked about some of the actions — {{define}} , {{template}} and {{block}} — but there are three more which you can use to control the display of dynamic data — {{if}} , {{with}} and {{range}} .
There are a few things about these actions to point out:
- For all three actions the {{else}} clause is optional. For instance, you can write {{if .Foo}} C1 {{end}} if there’s no C2 content that you want to render.
- The empty values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero.
- It’s important to grasp that the with and range actions change the value of dot. Once you start using them, what dot represents can be different depending on where you are in the template and what you’re doing.
The html/template package also provides some template functions which you can use to add extra logic to your templates and control what is rendered at runtime. You can find a complete listing of functions here (https://pkg.go.dev/text/template/#hdr-Functions), but the most important ones are:
The final row is an example of declaring a template variable. Template variables are particularly useful if you want to store the result from a function and use it in multiple places in your template. Variable names must be prefixed by a dollar sign and can contain alphanumeric characters only.
Using the with action
{{define "title"}}Snippet #{{.Snippet.ID}}{{end}} {{define "main"}} <main> {{with .Snippet}} <div class="snippet"> <div class="metadata"> <strong>{{.Title}}</strong> <span>#{{.ID}}</span> </div> <pre><code>{{.Content}}</code></pre> <div class="metadata"> <time>Created: {{.Created}}</time> <time>Expires: {{.Expires}}</time> </div> </div> {{end}} </main> {{end}}
Using the if and range actions
First update the templateData struct so that it contains a Snippets field for holding a slice of snippets, like so:
type templateData struct { Snippet models.Snippet Snippets []models.Snippet }
Then update the home handler function so that it fetches the latest snippets from our database model and passes them to the home.html template:
func (app *application) home(w http.ResponseWriter, r *http.Request) { w.Header().Add("Server", "Go") snippets, err := app.snippet.Latest() if err != nil { app.serverError(w, r, err) return } files := []string{ "./ui/html/base.html", "./ui/html/partials/nav.html", "./ui/html/pages/home.html", } ts, err := template.ParseFiles(files...) if err != nil { app.serverError(w, r, err) return } // Create an instance of a templateData struct holding the slice of snippets. data := templateData{ Snippets: snippets, } // Pass in the templateData struct when executing the template. err = ts.ExecuteTemplate(w, "base", data) if err != nil { app.serverError(w, r, err) } }
Now let’s head over to the ui/html/pages/home.html file and update it to display these snippets in a table using the {{if}} and {{range}} actions. Specifically:
- We want to use the {{if}} action to check whether the slice of snippets is empty or not. If it’s empty, we want to display a "There's nothing to see here yet! message. Otherwise, we want to render a table containing the snippet information.
- We want to use the {{range}} action to iterate over all snippets in the slice, rendering the contents of each snippet in a table row.
{{define "title"}}Home{{end}} {{define "main"}} <main> <h2>Latest Snippets</h2> {{if .Snippets}} <table> <tr> <th>Title</th> <th>Created</th> <th>ID</th> </tr> {{range .Snippets}} <tr> <td><a href="/snippet/view/{{.ID}}">{{.Title}}</a></td> <td>{{.Created}}</td> <td>#{{.ID}}</td> </tr> {{end}} </table> {{else}} <p>There's nothing to see here yet!</p> {{end}} </main> {{end}}
Combining functions
It’s possible to combine multiple functions in your template tags, using the parentheses () to surround the functions and their arguments as necessary.
For example, the following tag will render the content C1 if the length of Foo is greater than 99:
{{if (gt (len .Foo) 99)}} C1 {{end}}
Or as another example, the following tag will render the content C1 if .Foo equals 1 and .Bar is less than or equal to 20:
{{if (and (eq .Foo 1) (le .Bar 20))}} C1 {{end}}
Controlling loop behavior
Within a {{range}} action you can use the {{break}} command to end the loop early, and {{continue}} to immediately start the next loop iteration.
Caching templates
Before we add any more functionality to our HTML templates, it’s a good time to make some optimizations to our codebase. There are two main issues at the moment:
1. Each and every time we render a web page, our application reads and parses the relevant template files using the template.ParseFiles() function. We could avoid this duplicated work by parsing the files once — when starting the application — and storing the parsed templates in an in-memory cache.
2. There’s duplicated code in the home and snippetView handlers, and we could reduce this duplication by creating a helper function.
Let’s tackle the first point first, and create an in-memory map with the type map[string]*template.Template to cache the parsed templates. Open your cmd/web/templates.go file and add the following code:
func newTemplateCache() (map[string]*template.Template, error) { // Initialize a new map to act as the cache. cache := map[string]*template.Template{} // Use the filepath.Glob() function to get a slice of all filepaths // that match the pattern "./ui/html/pages/*.html". This will essentially // gives us a slice of all the filepaths for our application 'page' // tempplate. pages, err := filepath.Glob("./ui/html/pages/*.html") if err != nil { return nil, err } // Loop through the page filepaths one by one. for _, page := range pages { // Extract the file name from the full filepath and assign it to // the name variable. name := filepath.Base(page) // Create a slice containing the filepaths for our base template, // any partials and the page. files := []string{ "./ui/html/base.html", "./ui/html/partials/nav.html", page, } // Parse the files into a template set. ts, err := template.ParseFiles(files...) if err != nil { return nil, err } // Add the template set to the map, using the name of the page // as the key. cache[name] = ts } return cache, nil }
The next step is to initialize this cache in the main() function and make it available to our handlers as a dependency via the application struct, like this:
type application struct { logger *slog.Logger snippet *models.SnippetModel templateCache map[string]*template.Template } func main() { addr := flag.String("addr", ":4000", "HTTP network address") dbDriver := flag.String("dbdriver", "mysql", "Database driver name") dsn := flag.String("dsn", "zeb:zebpwd@tcp(localhost:3306)/snippetbox?parseTime=true", "MySQL data source name") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) db, err := openDB(*dbDriver, *dsn) if err != nil { logger.Error(err.Error()) os.Exit(1) } defer db.Close() // Initialize a new template cache. templateCache, err := newTemplateCache() if err != nil { logger.Error(err.Error()) os.Exit(1) } app := &application{ logger: logger, snippet: &models.SnippetModel{DB: db}, templateCache: templateCache, } logger.Info("starting server", "addr", *addr) err = http.ListenAndServe(*addr, app.routes()) logger.Error(err.Error()) os.Exit(1) }
So, at this point, we’ve got an in-memory cache of the relevant template set for each of our pages, and our handlers have access to this cache via the application struct.
Let’s now tackle the second issue of duplicated code, and create a helper method so that we can easily render the templates from the cache.
Open up your cmd/web/helpers.go file and add the following render() method:
func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) { // Retrieve the appropriate template set from the cache based on the page name. // If no entry exists in the cache with the provided name, then create a new // error and call the serverError() helper method. ts, ok := app.templateCache[page] if !ok { err := fmt.Errorf("the template %s does not exist", page) app.serverError(w, r, err) return } w.WriteHeader(status) err := ts.ExecuteTemplate(w, "base", data) if err != nil { app.serverError(w, r, err) } }
With that complete, we now get to see the payoff from these changes and can dramatically simplify the code in our handlers:
func (app *application) home(w http.ResponseWriter, r *http.Request) { w.Header().Add("Server", "Go") snippets, err := app.snippet.Latest() if err != nil { app.serverError(w, r, err) return } // Use the new render helper. app.render(w, r, http.StatusOK, "home.html", templateData{ Snippets: snippets, }) } func (app *application) snippetView(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil || id < 1 { http.NotFound(w, r) return } snippet, err := app.snippet.Get(id) if err != nil { if errors.Is(err, models.ErrNoRecord) { http.NotFound(w, r) } else { app.serverError(w, r, err) } return } app.render(w, r, http.StatusOK, "view.html", templateData{ Snippet: snippet, }) }
Automatically parsing partials
Before we move on, let’s make our newTemplateCache() function a bit more flexible so that it automatically parses all templates in the ui/html/partials folder — rather than only our nav.html file.
This will save us time, typing and potential bugs if we want to add additional partials in the future.
func newTemplateCache() (map[string]*template.Template, error) { cache := map[string]*template.Template{} pages, err := filepath.Glob("./ui/html/pages/*.html") if err != nil { return nil, err } for _, page := range pages { name := filepath.Base(page) // Parse the base template file into a template set. ts, err := template.ParseFiles("./ui/html/base.html") if err != nil { return nil, err } // Call ParseGlob() *on this template set* to add any partials. ts, err = ts.ParseGlob("./ui/html/partials/*.html") if err != nil { return nil, err } // Call ParseFiles() *on this template set* to add the page template. ts, err = ts.ParseFiles(page) if err != nil { return nil, err } cache[name] = ts } return cache, nil }
Catching runtime errors
As soon as we begin adding dynamic behavior to our HTML templates there’s a risk of encountering runtime errors.
Let’s add a deliberate error to the view.html template and see what happens:
{{define "title"}}Snippet #{{.Snippet.ID}}{{end}} {{define "main"}} <main> {{with .Snippet}} <div class="snippet"> <div class="metadata"> <strong>{{.Title}}</strong> <span>#{{.ID}}</span> </div> {{len nil}} <!-- Deliberate error --> <pre><code>{{.Content}}</code></pre> <div class="metadata"> <time>Created: {{.Created}}</time> <time>Expires: {{.Expires}}</time> </div> </div> {{end}} </main> {{end}}
In this markup above we’ve added the line {{len nil}} , which should generate an error at runtime because in Go the value nil does not have a length.
Try running the application now. You’ll find that everything still compiles OK:
zzh@ZZHPC:/zdata/Github/snippetbox$ go run ./cmd/web time=2024-09-04T11:48:00.022+08:00 level=INFO msg="starting server" addr=:4000
But if you visit http://localhost:4000/snippet/view/1 in browser, you’ll get a page which looks like this.
This is pretty bad. Our application has thrown an error, but the user has wrongly been sent a 200 OK response. And even worse, they’ve received a half-complete HTML page.
zzh@ZZHPC:/zdata/Github/snippetbox$ go run ./cmd/web time=2024-09-04T11:48:00.022+08:00 level=INFO msg="starting server" addr=:4000 time=2024-09-04T11:48:54.902+08:00 level=ERROR msg="template: view.html:10:10: executing \"main\" at <len nil>: error calling len: reflect: call of reflect.Value.Type on zero Value" method=GET uri=/snippet/view/1 2024/09/04 11:48:54 http: superfluous response.WriteHeader call from main.(*application).serverError (helpers.go:18)
To fix this we need to make the template render a two-stage process. First, we should make a ‘trial’ render by writing the template into a buffer. If this fails, we can respond to the user with an error message. But if it works, we can then write the contents of the buffer to our http.ResponseWriter .
Let’s update the render() helper to use this approach instead:
func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) { ts, ok := app.templateCache[page] if !ok { err := fmt.Errorf("the template %s does not exist", page) app.serverError(w, r, err) return } // Initialize a new buffer. buf := new(bytes.Buffer) // Write the template to the buffer, instead of straight to the // http.ResponseWriter. If there's an error, call serverError() // and then return. err := ts.ExecuteTemplate(buf, "base", data) if err != nil { app.serverError(w, r, err) return } // If the template is written to the buffer without any errors, // we are safe to go ahead and write the HTTP status code to // http.ResponseWriter. w.WriteHeader(status) // Write the contents of the buffer to the http.ResponseWriter. // Note: this is another time where we pass our http.ResponseWriter // to a function that takes an io.Writer. buf.WriteTo(w) }
Common dynamic data
In some web applications there may be common dynamic data that you want to include on more than one — or even every — webpage. For example, you might want to include the name and profile picture of the current user, or a CSRF token in all pages with forms.
In our case let’s begin with something simple, and say that we want to include the current year in the footer on every page.
To do this we’ll begin by adding a new CurrentYear field to the templateData struct, like so:
type templateData struct { CurrentYear int Snippet models.Snippet Snippets []models.Snippet }
The next step is to add a newTemplateData() helper method to our application, which will return a templateData struct initialized with the current year.
// newTemplateData returns a templateData struct initialized with the current year. Note that we're not using // the *http.Request parameter here at the moment, but we will do. func (app *application) newTemplateData(r *http.Request) templateData { return templateData{ CurrentYear: time.Now().Year(), } }
Then let’s update our home and snippetView handlers to use the newTemplateData() helper, like so:
func (app *application) home(w http.ResponseWriter, r *http.Request) { w.Header().Add("Server", "Go") snippets, err := app.snippet.Latest() if err != nil { app.serverError(w, r, err) return } // Call the newTemplateData() helper to get a templateData struct // containing the default data (which for now is just the current year), // and add the snippets slice to it. data := app.newTemplateData(r) data.Snippets = snippets // Pass the data to the render() helper as normal. app.render(w, r, http.StatusOK, "home.html", data) } func (app *application) snippetView(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil || id < 1 { http.NotFound(w, r) return } snippet, err := app.snippet.Get(id) if err != nil { if errors.Is(err, models.ErrNoRecord) { http.NotFound(w, r) } else { app.serverError(w, r, err) } return } data := app.newTemplateData(r) data.Snippet = snippet app.render(w, r, http.StatusOK, "view.html", data) }
And then the final thing we need to do is update the ui/html/base.html file to display the year in the footer, like so:
{{define "base"}} <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>{{template "title" .}} - Snippetbox</title> <link rel="stylesheet" href="/static/css/main.css"> <link rel="shortcut icon" href="/static/img/favicon.ico" type="image/x-icon"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700"> </head> <body> <header> <h1><a href="/">Snippetbox</a></h1> </header> {{template "nav" .}} <!-- every template action or function automatically adds a blank line before and after its block--> {{template "main" .}} <!-- so there will be two blank lines between the nav block and the main block --> <footer> Powered by <a href="https://golang.org/">Go</a> in {{.CurrentYear}} </footer> <script src="/static/js/main.js" type="text/javascript"></script> </body> </html> {{end}}
Custom template functions
I’d like to explain how to create your own custom functions to use in Go templates.
To illustrate this, let’s create a custom humanDate() function which outputs datetimes in a nice ‘humanized’ format like 1 Jan 2024 at 10:47 or 18 Mar 2024 at 15:04 instead of outputting dates in the default format of YYYY-MM-DD HH:MM:SS +0000 UTC like we are currently.
There are two main steps to doing this:
1. We need to create a template.FuncMap object containing the custom humanDate() function.
2. We need to use the template.Funcs() method to register this before parsing the templates.
Go ahead and add the following code to your templates.go file:
// humanDate returns a nicely formatted string representation of a time.Time // object. func humanDate(t time.Time) string { return t.Format("02 Jan 2006 at 15:04") } // Initialize a template.FuncMap object and store it in a global variable. // This is essentially a string-keyed map which acts as a lookup between // the names of our custom template functions and the functions themselves. var functions = template.FuncMap{ "humanDate": humanDate, } func newTemplateCache() (map[string]*template.Template, error) { cache := map[string]*template.Template{} pages, err := filepath.Glob("./ui/html/pages/*.html") if err != nil { return nil, err } for _, page := range pages { name := filepath.Base(page) // The template.FuncMap must be registered with the template set // before you call the ParseFiles() method. This means we have to // use template.New() to create an empty template set, use the Funcs() // method to register the template.FuncMap, and then parse the file // as normal. ts, err := template.New(name).Funcs(functions).ParseFiles("./ui/html/base.html") if err != nil { return nil, err } ts, err = ts.ParseGlob("./ui/html/partials/*.html") if err != nil { return nil, err } ts, err = ts.ParseFiles(page) if err != nil { return nil, err } cache[name] = ts } return cache, nil }
Before we continue, I should explain: custom template functions (like our humanDate() function) can accept as many parameters as they need to, but they must return one value only. The only exception to this is if you want to return an error as the second value, in which case that’s OK too.
Now we can use our humanDate() function in the same way as the built-in template functions:
{{define "title"}}Home{{end}} {{define "main"}} <main> <h2>Latest Snippets</h2> {{if .Snippets}} <table> <tr> <th>Title</th> <th>Created</th> <th>ID</th> </tr> {{range .Snippets}} <tr> <td><a href="/snippet/view/{{.ID}}">{{.Title}}</a></td> <!-- Use the new template function here --> <td>{{humanDate .Created}}</td> <td>#{{.ID}}</td> </tr> {{end}} </table> {{else}} <p>There's nothing to see here yet!</p> {{end}} </main> {{end}}
Pipelining
In the code above, we called our custom template function like this:
<time>Created: {{humanDate .Created}}</time>
An alternative approach is to use the | character to pipeline values to a function. This works a bit like pipelining outputs from one command to another in Unix terminals. We could re-write the above as:
<time>Created: {{.Created | humanDate}}</time
A nice feature of pipelining is that you can make an arbitrarily long chain of template functions which use the output from one as the input for the next. For example, we could pipeline the output from our humanDate function to the inbuilt printf function like so:
<time>{{.Created | humanDate | printf "Created: %s"}}</time>