go 58 lines · 8 steps

Per-tenant HTML templates with Gin's renderer

A thread-safe registry gives each tenant its own template set while satisfying Gin's HTMLRender interface.

Explained by highlit
1package theme
2 
3import (
4 "fmt"
5 "html/template"
6 "path/filepath"
7 "sync"
8 
9 "github.com/gin-gonic/gin/render"
10)
11 
12type Registry struct {
13 mu sync.RWMutex
14 tenants map[string]*template.Template
15 funcs template.FuncMap
16 baseGlob string
17}
18 
19func NewRegistry(baseGlob string, funcs template.FuncMap) *Registry {
20 return &Registry{
21 tenants: make(map[string]*template.Template),
22 funcs: funcs,
23 baseGlob: baseGlob,
24 }
25}
26 
27func (r *Registry) Load(tenant, dir string) error {
28 tmpl := template.New(tenant).Funcs(r.funcs)
29 
30 if _, err := tmpl.ParseGlob(r.baseGlob); err != nil {
31 return fmt.Errorf("parse base templates: %w", err)
32 }
33 if _, err := tmpl.ParseGlob(filepath.Join(dir, "*.html")); err != nil {
34 return fmt.Errorf("parse tenant %q templates: %w", tenant, err)
35 }
36 
37 r.mu.Lock()
38 r.tenants[tenant] = tmpl
39 r.mu.Unlock()
40 return nil
41}
42 
43func (r *Registry) Instance(name string, data any) render.Render {
44 tenant, _ := data.(map[string]any)["tenant"].(string)
45 
46 r.mu.RLock()
47 tmpl, ok := r.tenants[tenant]
48 if !ok {
49 tmpl = r.tenants["default"]
50 }
51 r.mu.RUnlock()
52 
53 return render.HTML{
54 Template: tmpl,
55 Name: name,
56 Data: data,
57 }
58}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Implementing Gin's HTMLRender interface lets you swap in fully custom, per-request template selection.
  2. 2An RWMutex lets many requests read the template map concurrently while loads take exclusive access.
  3. 3Layering ParseGlob calls composes shared base templates with tenant-specific overrides in one template set.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Per-tenant HTML templates with Gin's renderer — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code