Templating
Okapi supports template rendering for serving HTML pages. You can use the built-in renderer, Go’s standard html/template package, or any custom renderer that implements Okapi’s Renderer interface.
Built-in Template Renderer
The simplest way to get started. NewTemplateFromDirectory scans a folder for files matching the given extensions and registers them by filename (without extension) as named templates.
func main() {
// Load all .html and .tmpl files from the views directory
tmpl, err := okapi.NewTemplateFromDirectory("public/views", ".html", ".tmpl")
if err != nil {
log.Fatal(err)
}
o := okapi.Default().WithRenderer(tmpl)
o.Get("/", func(c *okapi.Context) error {
return c.Render(http.StatusOK, "hello", okapi.M{
"title": "Greeting Page",
"message": "Hello, World!",
})
})
if err := o.Start(); err != nil {
log.Fatal(err)
}
}
You can also load templates from a glob pattern instead of a directory:
tmpl, err := okapi.NewTemplateFromFiles("public/views/*.html")
Embedded Templates
For production deployments, embedding templates directly into the binary using Go’s embed package eliminates runtime file-system dependencies.
var (
//go:embed views/*
Views embed.FS
AssetsFS = http.FS(must(fs.Sub(Views, "views/assets")))
)
func main() {
app := okapi.New()
app.WithRendererFromFS(Views, "views/*.html")
app.Get("/", func(c *okapi.Context) error {
return c.Render(http.StatusOK, "home", okapi.M{
"title": "Go Okapi Bookstore",
"headline": "Discover your next great read",
"books": books,
})
})
// Serve embedded static assets
app.StaticFS("/assets", AssetsFS)
if err := app.Start(); err != nil {
panic(err)
}
}
func must(fsys fs.FS, err error) fs.FS {
if err != nil {
panic(err)
}
return fsys
}
Custom Renderers
If the built-in renderer doesn’t fit your needs — for example, you want to use a third-party templating engine like Sprig or Petal — you can supply your own. Okapi supports two approaches.
Renderer Function
Use RendererFunc for lightweight, one-off renderers without needing a full struct:
o.Renderer = okapi.RendererFunc(func(w io.Writer, name string, data interface{}, c *okapi.Context) error {
tmpl, err := template.ParseFiles("templates/" + name + ".html")
if err != nil {
return err
}
return tmpl.ExecuteTemplate(w, name, data)
})
Warning: This example re-parses the template file on every request. In production, parse templates once at startup and cache the result (see the struct-based example below).
Struct-Based Renderer
For more control — caching, shared state, or preloaded templates — implement the Renderer interface on a struct:
type Template struct {
templates *template.Template
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c *okapi.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
// At startup:
tmpl := &Template{
templates: template.Must(template.ParseGlob("templates/*.html")),
}
o := okapi.New().WithRenderer(tmpl)
Templates are parsed once via ParseGlob and reused for every request, which is the recommended approach for production.
Rendering a View
Once a renderer is attached, use c.Render inside any handler to render a named template with arbitrary data:
o.Get("/welcome", func(c *okapi.Context) error {
return c.Render(http.StatusOK, "welcome", okapi.M{
"title": "Welcome Page",
"message": "Hello from Okapi!",
})
})
okapi.M is a shorthand for map[string]interface{}. Each key becomes accessible inside the template as ``.
Example Template
templates/welcome.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
</head>
<body>
<h1></h1>
</body>
</html>
Static File Serving
Okapi can serve static assets alongside your rendered pages.
Serve a directory
All files under public/assets become accessible at /static/*:
o.Static("/static", "public/assets")
Serve a single file
o.Get("/favicon.ico", func(c *okapi.Context) error {
c.ServeFile("public/favicon.ico")
return nil
})
Serve an embedded filesystem
Use StaticFS to serve assets that were compiled into the binary via embed:
app.StaticFS("/assets", AssetsFS)
This pairs naturally with the Embedded Templates example above, where AssetsFS is derived from the same embed.FS.