Gin Multiple Templates
Introduction
When building web applications with the Gin framework in Go, you'll often need more than just a single HTML template. As your application grows, you might want to structure your frontend with layouts, partials, and various page templates. Gin provides a powerful way to work with multiple templates, allowing you to create modular and maintainable web interfaces.
In this guide, we'll learn how to:
- Set up multiple templates in a Gin application
- Create and use layouts with template inheritance
- Organize templates in different directories
- Pass data to multiple templates
- Handle template rendering with various strategies
Understanding Multiple Templates in Gin
By default, Gin uses the standard Go template engine. While you can load a single template with router.LoadHTMLGlob()
or router.LoadHTMLFiles()
, more complex applications benefit from using multiple templates with inheritance and includes.
Why Use Multiple Templates?
- Code Reusability: Define common elements like headers and footers once
- Separation of Concerns: Keep different page content in separate files
- Maintainability: Easier to update specific parts of your UI
- Organization: Better structure for complex web applications
Basic Setup for Multiple Templates
Let's start with a basic setup for handling multiple templates in a Gin application:
package main
import (
"github.com/gin-gonic/gin"
"html/template"
"net/http"
)
func main() {
router := gin.Default()
// Load templates from different directories
router.LoadHTMLGlob("templates/**/*")
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Home Page",
})
})
router.GET("/about", func(c *gin.Context) {
c.HTML(http.StatusOK, "about.tmpl", gin.H{
"title": "About Page",
})
})
router.Run(":8080")
}
In this example, we're using LoadHTMLGlob("templates/**/*")
which loads all templates from any subdirectory within the "templates" directory.
Organizing Your Templates
A common structure for templates in a web application might look like:
templates/
├── layouts/
│ └── base.tmpl
├── partials/
│ ├── header.tmpl
│ └── footer.tmpl
└── pages/
├── index.tmpl
└── about.tmpl
Creating a Base Layout
Let's create a base layout template (templates/layouts/base.tmpl
):
<!DOCTYPE html>
<html>
<head>
<title>{{ .title }}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
<header>
{{ template "header.tmpl" . }}
</header>
<main>
{{ template "content" . }}
</main>
<footer>
{{ template "footer.tmpl" . }}
</footer>
<script src="/static/js/main.js"></script>
</body>
</html>
Creating Partial Templates
Now, let's create header and footer partials (templates/partials/header.tmpl
):
<nav>
<div class="logo">My Gin App</div>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
And the footer (templates/partials/footer.tmpl
):
<div class="footer-content">
<p>© {{ .year }} My Gin Application. All rights reserved.</p>
</div>
Creating Page Templates
Now, for our actual pages (templates/pages/index.tmpl
):
{{ define "content" }}
<div class="container">
<h1>{{ .title }}</h1>
<p>Welcome to our Gin application using multiple templates!</p>
<div class="features">
<div class="feature">
<h3>Fast</h3>
<p>Gin is one of the fastest Go web frameworks available.</p>
</div>
<div class="feature">
<h3>Flexible</h3>
<p>Create complex web applications with ease.</p>
</div>
</div>
</div>
{{ end }}
And the about page (templates/pages/about.tmpl
):
{{ define "content" }}
<div class="container">
<h1>{{ .title }}</h1>
<p>This application demonstrates how to use multiple templates in Gin.</p>
<div class="about-content">
<h2>Our Story</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce at diam tellus.</p>
</div>
</div>
{{ end }}
Loading Templates with Custom Functions
For more complex applications, you might want to create a custom template loader with functions:
package main
import (
"github.com/gin-gonic/gin"
"html/template"
"net/http"
"time"
)
func main() {
router := gin.Default()
// Create a template with custom functions
tmpl := template.Must(template.New("").
Funcs(template.FuncMap{
"formatDate": func(t time.Time) string {
return t.Format("Jan 02, 2006")
},
"safeHTML": func(s string) template.HTML {
return template.HTML(s)
},
}).
ParseGlob("templates/layouts/*.tmpl"))
// Load partials
template.Must(tmpl.ParseGlob("templates/partials/*.tmpl"))
// Load page templates
template.Must(tmpl.ParseGlob("templates/pages/*.tmpl"))
// Set the template to Gin
router.SetHTMLTemplate(tmpl)
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "base.tmpl", gin.H{
"title": "Home Page",
"year": time.Now().Year(),
"content": "index.tmpl",
})
})
router.GET("/about", func(c *gin.Context) {
c.HTML(http.StatusOK, "base.tmpl", gin.H{
"title": "About Us",
"year": time.Now().Year(),
"content": "about.tmpl",
})
})
router.Run(":8080")
}
Advanced Template Rendering
Using Template Inheritance with Blocks
Another approach is to use block-based inheritance. Update your templates to use this pattern:
base.tmpl:
{{ define "base" }}
<!DOCTYPE html>
<html>
<head>
<title>{{ block "title" . }}Default Title{{ end }}</title>
<meta charset="utf-8">
<link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
<header>{{ block "header" . }}{{ end }}</header>
<main>{{ block "content" . }}{{ end }}</main>
<footer>{{ block "footer" . }}{{ end }}</footer>
</body>
</html>
{{ end }}
index.tmpl:
{{ define "index" }}
{{ template "base" . }}
{{ end }}
{{ define "title" }}Home Page{{ end }}
{{ define "header" }}
<nav>Home | <a href="/about">About</a></nav>
{{ end }}
{{ define "content" }}
<h1>Welcome to the Home Page</h1>
<p>This is the main content of our home page.</p>
{{ end }}
{{ define "footer" }}
<p>© {{ .year }} My Application</p>
{{ end }}
And then in your Go code:
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index", gin.H{
"year": time.Now().Year(),
})
})
Real-world Example: A Blog Platform
Let's create a more comprehensive example of a simple blog platform with multiple templates:
Directory Structure
templates/
├── layouts/
│ └── main.tmpl
├── partials/
│ ├── header.tmpl
│ ├── footer.tmpl
│ └── sidebar.tmpl
└── pages/
├── home.tmpl
├── post.tmpl
└── author.tmpl
Sample Code for Blog
package main
import (
"github.com/gin-gonic/gin"
"html/template"
"net/http"
"time"
)
// Post represents a blog post
type Post struct {
ID int
Title string
Content string
CreatedAt time.Time
Author string
}
// PostData returns sample blog posts
func PostData() []Post {
return []Post{
{
ID: 1,
Title: "Getting Started with Gin",
Content: "Gin is a web framework written in Go...",
CreatedAt: time.Now().AddDate(0, 0, -2),
Author: "Jane Doe",
},
{
ID: 2,
Title: "Working with Templates",
Content: "Templates in Gin follow the standard Go template syntax...",
CreatedAt: time.Now().AddDate(0, 0, -1),
Author: "John Smith",
},
}
}
func main() {
router := gin.Default()
// Custom template functions
funcMap := template.FuncMap{
"formatDate": func(t time.Time) string {
return t.Format("January 2, 2006")
},
"excerpt": func(s string) string {
if len(s) > 100 {
return s[:97] + "..."
}
return s
},
}
// Load all templates with our custom functions
tmpl := template.Must(template.New("").Funcs(funcMap).ParseGlob("templates/layouts/*.tmpl"))
template.Must(tmpl.ParseGlob("templates/partials/*.tmpl"))
template.Must(tmpl.ParseGlob("templates/pages/*.tmpl"))
router.SetHTMLTemplate(tmpl)
// Serve static files
router.Static("/static", "./static")
// Routes
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "main.tmpl", gin.H{
"title": "Blog Home",
"description": "Latest articles about Gin framework",
"posts": PostData(),
"page": "home.tmpl",
"year": time.Now().Year(),
})
})
router.GET("/post/:id", func(c *gin.Context) {
id := c.Param("id")
var post Post
// In a real app, you'd fetch this from a database
for _, p := range PostData() {
if p.ID == 1 { // Just for example, using ID=1
post = p
break
}
}
c.HTML(http.StatusOK, "main.tmpl", gin.H{
"title": post.Title,
"description": "Read the full article",
"post": post,
"page": "post.tmpl",
"year": time.Now().Year(),
})
})
router.Run(":8080")
}
Our layout template (templates/layouts/main.tmpl
):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .title }} | My Blog</title>
<meta name="description" content="{{ .description }}">
<link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
{{ template "header.tmpl" . }}
<div class="container">
<div class="row">
<div class="col-md-8">
{{ template .page . }}
</div>
<div class="col-md-4">
{{ template "sidebar.tmpl" . }}
</div>
</div>
</div>
{{ template "footer.tmpl" . }}
<script src="/static/js/main.js"></script>
</body>
</html>
And the home page template (templates/pages/home.tmpl
):
<h1>Latest Blog Posts</h1>
<div class="posts">
{{ range .posts }}
<article class="post-summary">
<h2><a href="/post/{{ .ID }}">{{ .Title }}</a></h2>
<p class="meta">By {{ .Author }} on {{ formatDate .CreatedAt }}</p>
<p>{{ excerpt .Content }}</p>
<a href="/post/{{ .ID }}" class="read-more">Read More</a>
</article>
{{ end }}
</div>
Advanced Techniques
Caching Templates in Production
In production environments, it's a good practice to cache your templates to avoid reloading them on every request:
var templates map[string]*template.Template
func loadTemplates() {
if gin.IsDebugging() {
// In development mode, load templates on every request
return
}
// In production, load once
templates = make(map[string]*template.Template)
// Load various template combinations
templates["home"] = template.Must(template.ParseFiles(
"templates/layouts/base.tmpl",
"templates/partials/header.tmpl",
"templates/partials/footer.tmpl",
"templates/pages/home.tmpl",
))
templates["about"] = template.Must(template.ParseFiles(
"templates/layouts/base.tmpl",
"templates/partials/header.tmpl",
"templates/partials/footer.tmpl",
"templates/pages/about.tmpl",
))
}
func renderTemplate(c *gin.Context, name string, data gin.H) {
if gin.IsDebugging() {
// In development, use Gin's HTML renderer
c.HTML(http.StatusOK, name+".tmpl", data)
return
}
// In production, use cached templates
tmpl, ok := templates[name]
if !ok {
c.String(http.StatusInternalServerError, "Template not found")
return
}
c.Status(http.StatusOK)
c.Header("Content-Type", "text/html")
err := tmpl.Execute(c.Writer, data)
if err != nil {
c.Error(err)
}
}
Adding Hot Reloading for Development
During development, it's helpful to have templates automatically reload when they change:
package main
import (
"github.com/gin-contrib/multitemplate"
"github.com/gin-gonic/gin"
"net/http"
"path/filepath"
)
// createRenderer creates a multitemplate renderer that reloads templates
func createRenderer() multitemplate.Renderer {
r := multitemplate.NewRenderer()
// Add base layouts
layouts, err := filepath.Glob("templates/layouts/*.tmpl")
if err != nil {
panic(err.Error())
}
// Add pages with their layouts
pages, err := filepath.Glob("templates/pages/*.tmpl")
if err != nil {
panic(err.Error())
}
// Generate our templates map
for _, page := range pages {
fileName := filepath.Base(page)
// Create each page with all layouts
templateFiles := append(layouts, page)
// Add partials
partials, err := filepath.Glob("templates/partials/*.tmpl")
if err != nil {
panic(err.Error())
}
templateFiles = append(templateFiles, partials...)
r.AddFromFiles(fileName, templateFiles...)
}
return r
}
func main() {
router := gin.Default()
// Set the renderer
router.HTMLRender = createRenderer()
router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Home Page",
})
})
router.GET("/about", func(c *gin.Context) {
c.HTML(http.StatusOK, "about.tmpl", gin.H{
"title": "About Us",
})
})
router.Run(":8080")
}
Summary
Working with multiple templates in Gin allows you to build complex, modular web applications with clean and maintainable code. Key points to remember:
- Use
LoadHTMLGlob()
to load templates from multiple directories - Organize templates into layouts, partials, and pages for better structure
- Use template inheritance to avoid code duplication
- Pass data from your Gin handlers to populate templates
- Consider using custom template functions to enhance your templates
- Implement caching for production environments
- Use hot reloading during development
By mastering multiple templates in Gin, you can create sophisticated web interfaces while keeping your code organized and maintainable.
Additional Resources
- Gin Framework Official Documentation
- Go Templates Documentation
- Gin-Contrib Multitemplate - A middleware for Gin that allows multiple template engines
Exercises
- Create a portfolio website using Gin with multiple templates that includes a home page, projects page, and contact page.
- Implement a blog system that uses layouts and partials to display blog posts and categories.
- Add custom template functions that format dates, truncate text, and provide other common utilities.
- Create a template caching system that reloads templates only in development mode.
- Implement a theme system that allows users to switch between different layouts for your Gin application.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)