Skip to main content

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:

go
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):

html
<!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):

html
<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):

html
<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):

html
{{ 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):

html
{{ 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:

go
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:

html
{{ 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:

html
{{ 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:

go
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

go
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):

html
<!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):

html
<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:

go
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:

go
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:

  1. Use LoadHTMLGlob() to load templates from multiple directories
  2. Organize templates into layouts, partials, and pages for better structure
  3. Use template inheritance to avoid code duplication
  4. Pass data from your Gin handlers to populate templates
  5. Consider using custom template functions to enhance your templates
  6. Implement caching for production environments
  7. 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

  1. Gin Framework Official Documentation
  2. Go Templates Documentation
  3. Gin-Contrib Multitemplate - A middleware for Gin that allows multiple template engines

Exercises

  1. Create a portfolio website using Gin with multiple templates that includes a home page, projects page, and contact page.
  2. Implement a blog system that uses layouts and partials to display blog posts and categories.
  3. Add custom template functions that format dates, truncate text, and provide other common utilities.
  4. Create a template caching system that reloads templates only in development mode.
  5. 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! :)