Core Framework

Views and Layouts

Render embedded html/template views through layouts with escaped data and explicit trusted HTML.

By Guillermo Alvarez - Published - Updated

Embed views

The application embeds its templates:

//go:embed views public
var Files embed.FS

app.Views returns the views subtree as an fs.FS. Startup passes that filesystem to lazyapp.New, which initializes the renderer.

The renderer requires a default layout:

layouts/app.html.tpl

Missing embedded resources fail during application initialization rather than on the first production request.

Name views by controller and action

Controller view paths follow:

app/views/<controller>/<action>.html.tpl

Given a request routed to PostsController, this call:

return c.Render("index")

loads:

posts/index.html.tpl

Set template data

Add values before rendering:

c.Set("title", "Posts")
c.Set("posts", c.posts.List())

Templates access them by name:

<h1>{{ .title }}</h1>

{{ range .posts }}
  <a href="/posts/{{ .Slug }}">{{ .Title }}</a>
{{ end }}

The same data map is available to the layout.

Compose the layout

GoLazy executes the controller view first. It then passes the generated content to the selected layout as .content:

<!doctype html>
<html>
  <head>
    <title>{{ .title }}</title>
  </head>
  <body>
    <main>{{ .content }}</main>
  </body>
</html>

Use SetLayout before Render to select a layout other than app.

Register helpers

Application helpers are plain Go functions. The sample app keeps them in app/helpers and returns them as a map:

func RegisterHelpers() map[string]any {
    return map[string]any{
        "word_count": WordCount,
        "read_time":  ReadTime,
    }
}

Startup passes that map to lazyapp.New:

return lazyapp.New(lazyapp.Config{
    Name:    "sample_app",
    Drawer:  Draw,
    Public:  app.Public,
    Views:   app.Views,
    Context: Context,
    Helpers: []map[string]any{helpers.RegisterHelpers()},
})

Templates call registered helpers by name:

{{ word_count .post.Body }} words
{{ read_time .post.Body }} min read

Router helpers such as path_for are registered automatically by lazyapp.

Escaping and trusted HTML

Go's html/template escapes ordinary data according to its HTML context. Keep user and application data as strings whenever possible.

Only convert HTML that was produced by a trusted renderer:

body, err := markdown.Convert(post.Body)
if err != nil {
    return fmt.Errorf("render post markdown: %w", err)
}

c.Set("body", template.HTML(body))

template.HTML disables escaping. Never use it merely to make untrusted input render as markup.

Rendering errors

Render returns contextual errors for:

  • Missing view files.
  • Invalid view templates.
  • View execution failures.
  • Missing or invalid layouts.
  • Layout execution failures.
  • Response write failures.

Return those errors from the action so the framework can produce the HTTP response.