Core Framework
Views and Layouts
Render embedded html/template views through layouts with escaped data and explicit trusted HTML.
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#Index, an action can set view data
and return nil:
c.Set("title", "Posts")
c.Set("posts", c.posts.List())
return nil
GoLazy then renders:
posts/index.html.tpl
Use Render("other") only when the action should render a non-default view.
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 helper 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: lazyapp.Helpers{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.
Asset helpers are registered automatically when public files or generated asset
sources are configured.
lazyapp.New registers router, asset, and application helpers before caching
templates. Treat the helper set as startup configuration. If you intentionally
change helpers after setup in a custom assembly, call Views.Cache again
before serving requests.
Link public assets
lazyapp registers public files with lazyassets and exposes helpers to
templates:
{{stylesheet "/styles.css"}}
stylesheet renders a trusted stylesheet link whose href uses asset_path.
asset_path returns the content-hashed permanent URL, such as
/styles-<hash>.css. The original logical path still works, but the permanent
URL can receive an immutable cache policy because the URL changes when the file
content changes.
Use asset_integrity when a tag needs the matching SHA-256 integrity value:
<link
rel="stylesheet"
href="{{asset_path "/styles.css"}}"
integrity="{{asset_integrity "/styles.css"}}">
permalink is kept as a compatibility alias for asset_path.
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.