Gin Template Inheritance
Template inheritance is a powerful feature that allows you to build a base "layout" template that contains all the common elements of your site and defines blocks that child templates can override. In this tutorial, we'll explore how to implement template inheritance in the Gin web framework to create DRY (Don't Repeat Yourself) templates and maintain a consistent look across your web application.
Introduction to Template Inheritance
When building web applications, you'll often have common elements that appear on multiple pages, such as headers, navigation bars, and footers. Instead of duplicating this code across every template, template inheritance allows you to:
- Define these common elements in a parent/base template
- Create child templates that inherit from the parent template
- Override specific sections as needed in child templates
Gin uses the standard Go html/template
package which supports template inheritance through a combination of template definitions, blocks, and includes.
Basic Template Inheritance in Gin
Setting Up the Template Structure
First, let's organize our template files:
templates/
├── layouts/
│ └── base.html
├── includes/
│ ├── header.html
│ └── footer.html
└── pages/
├── home.html
├── about.html
└── contact.html
Creating a Base Layout Template
Let's create a base layout template that will serve as the parent for all other templates:
<!-- templates/layouts/base.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ block "title" . }}My Gin Website{{ end }}</title>
<link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
<header>
{{ template "includes/header.html" . }}
</header>
<main>
{{ block "content" . }}
<p>Default content - this will be replaced by child templates</p>
{{ end }}
</main>
<footer>
{{ template "includes/footer.html" . }}
</footer>
<script src="/static/js/main.js"></script>
</body>
</html>
Creating Include Templates
Next, let's create the header and footer includes:
<!-- templates/includes/header.html -->
<nav>
<div class="logo">My Gin Website</div>
<ul class="menu">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<!-- templates/includes/footer.html -->
<div class="footer-content">
<p>© 2023 My Gin Website</p>
</div>
Creating Child Templates
Now, let's create some child templates that inherit from the base layout:
<!-- templates/pages/home.html -->
{{ define "title" }}Home | My Gin Website{{ end }}
{{ define "content" }}
<div class="home-page">
<h1>Welcome to My Gin Website</h1>
<p>This is the home page of our website built with Gin.</p>
<div class="featured-content">
<h2>Featured Content</h2>
<p>Here's some featured content that's specific to the home page.</p>
</div>
</div>
{{ end }}
<!-- templates/pages/about.html -->
{{ define "title" }}About | My Gin Website{{ end }}
{{ define "content" }}
<div class="about-page">
<h1>About Us</h1>
<p>Learn more about our company and our mission.</p>
<div class="team-section">
<h2>Our Team</h2>
<p>Meet the team behind this website.</p>
</div>
</div>
{{ end }}
Implementing in Gin
Now let's implement this template structure in our Gin application:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// Load all templates
r.LoadHTMLGlob("templates/**/*")
// Serve static files
r.Static("/static", "./static")
// Routes
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "pages/home.html", gin.H{
"title": "Home Page",
})
})
r.GET("/about", func(c *gin.Context) {
c.HTML(http.StatusOK, "pages/about.html", gin.H{
"title": "About Us",
})
})
r.GET("/contact", func(c *gin.Context) {
c.HTML(http.StatusOK, "pages/contact.html", gin.H{
"title": "Contact Us",
"email": "[email protected]",
})
})
r.Run(":8080")
}
Advanced Template Inheritance Techniques
Nested Blocks
You can create nested blocks for more granular control over template sections:
<!-- templates/layouts/base.html (partial) -->
<main>
{{ block "content" . }}
<p>Default content</p>
{{ end }}
<div class="sidebar">
{{ block "sidebar" . }}
<h3>Default Sidebar</h3>
<p>Sidebar content goes here</p>
{{ end }}
</div>
</main>
Then in your child templates:
<!-- templates/pages/about.html -->
{{ define "title" }}About | My Gin Website{{ end }}
{{ define "content" }}
<div class="about-page">
<h1>About Us</h1>
<p>Learn more about our company and our mission.</p>
</div>
{{ end }}
{{ define "sidebar" }}
<div class="about-sidebar">
<h3>About Sidebar</h3>
<ul>
<li><a href="#history">Our History</a></li>
<li><a href="#mission">Our Mission</a></li>
<li><a href="#team">Our Team</a></li>
</ul>
</div>
{{ end }}
Multiple Layout Templates
Sometimes you may need different layouts for different sections of your site. Let's create an admin layout:
<!-- templates/layouts/admin.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ block "title" . }}Admin Panel{{ end }}</title>
<link rel="stylesheet" href="/static/css/admin.css">
</head>
<body>
<header class="admin-header">
{{ template "includes/admin-header.html" . }}
</header>
<div class="admin-layout">
<aside class="admin-sidebar">
{{ block "admin-sidebar" . }}
<nav>
<ul>
<li><a href="/admin/dashboard">Dashboard</a></li>
<li><a href="/admin/users">Users</a></li>
<li><a href="/admin/settings">Settings</a></li>
</ul>
</nav>
{{ end }}
</aside>
<main class="admin-content">
{{ block "admin-content" . }}
<p>Default admin content</p>
{{ end }}
</main>
</div>
<footer class="admin-footer">
{{ template "includes/admin-footer.html" . }}
</footer>
</body>
</html>
Then create admin templates that use this layout:
<!-- templates/pages/admin/dashboard.html -->
{{ define "title" }}Dashboard | Admin Panel{{ end }}
{{ define "admin-content" }}
<div class="dashboard">
<h1>Admin Dashboard</h1>
<div class="stats">
<div class="stat-card">
<h3>Users</h3>
<p class="stat-number">{{ .userCount }}</p>
</div>
<div class="stat-card">
<h3>Posts</h3>
<p class="stat-number">{{ .postCount }}</p>
</div>
</div>
</div>
{{ end }}
Practical Example: Building a Blog with Template Inheritance
Let's bring everything together in a practical example of a blog application with different template types:
Project Structure
blog-app/
├── main.go
├── static/
│ ├── css/
│ │ ├── main.css
│ │ └── admin.css
│ └── js/
│ ├── main.js
│ └── admin.js
└── templates/
├── layouts/
│ ├── base.html
│ └── admin.html
├── includes/
│ ├── header.html
│ ├── footer.html
│ ├── admin-header.html
│ ├── admin-footer.html
│ └── post-card.html
└── pages/
├── home.html
├── blog-list.html
├── blog-single.html
├── admin/
│ ├── dashboard.html
│ ├── post-editor.html
│ └── user-management.html
└── error.html
Implementation in Gin
package main
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
// Simple post structure for demonstration
type Post struct {
ID int
Title string
Excerpt string
Content string
Author string
}
// Get sample blog posts
func getSamplePosts() []Post {
return []Post{
{1, "Getting Started with Gin", "Learn how to set up a basic Gin web application", "Full content here...", "John Doe"},
{2, "Template Inheritance in Gin", "How to implement template inheritance", "Full content here...", "Jane Smith"},
{3, "RESTful APIs with Gin", "Building robust APIs with the Gin framework", "Full content here...", "Bob Johnson"},
}
}
func main() {
r := gin.Default()
// Load templates
r.LoadHTMLGlob("templates/**/*")
// Serve static files
r.Static("/static", "./static")
// Public routes
r.GET("/", func(c *gin.Context) {
posts := getSamplePosts()
c.HTML(http.StatusOK, "pages/home.html", gin.H{
"featuredPosts": posts[:2],
})
})
r.GET("/blog", func(c *gin.Context) {
posts := getSamplePosts()
c.HTML(http.StatusOK, "pages/blog-list.html", gin.H{
"title": "Blog",
"posts": posts,
})
})
r.GET("/blog/:id", func(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.HTML(http.StatusNotFound, "pages/error.html", gin.H{
"message": "Post not found",
})
return
}
posts := getSamplePosts()
var post Post
found := false
for _, p := range posts {
if p.ID == id {
post = p
found = true
break
}
}
if !found {
c.HTML(http.StatusNotFound, "pages/error.html", gin.H{
"message": "Post not found",
})
return
}
c.HTML(http.StatusOK, "pages/blog-single.html", gin.H{
"title": post.Title,
"post": post,
})
})
// Admin routes
admin := r.Group("/admin")
{
admin.GET("/dashboard", func(c *gin.Context) {
c.HTML(http.StatusOK, "pages/admin/dashboard.html", gin.H{
"userCount": 42,
"postCount": len(getSamplePosts()),
})
})
admin.GET("/posts", func(c *gin.Context) {
posts := getSamplePosts()
c.HTML(http.StatusOK, "pages/admin/post-editor.html", gin.H{
"posts": posts,
})
})
admin.GET("/users", func(c *gin.Context) {
c.HTML(http.StatusOK, "pages/admin/user-management.html", gin.H{
"users": []string{"admin", "editor", "subscriber"},
})
})
}
r.Run(":8080")
}
Common Issues and Solutions
Issue 1: Templates Not Loading
If your templates aren't loading properly, double-check your file paths and the pattern passed to LoadHTMLGlob()
.
// This will load all templates in the templates directory and subdirectories
r.LoadHTMLGlob("templates/**/*")
// For specific templates, you might use:
r.LoadHTMLGlob("templates/layouts/*.html")
r.LoadHTMLGlob("templates/pages/*.html")
r.LoadHTMLGlob("templates/includes/*.html")
Issue 2: Template Not Found Error
If you see a "template not found" error, make sure you're using the correct template name in your handler:
// The template name should match the file path relative to your templates directory
c.HTML(http.StatusOK, "pages/home.html", gin.H{})
// Not just "home.html"
Issue 3: Block Definitions Not Working
Make sure your base template includes block definitions and your child templates correctly define those blocks:
<!-- Base template -->
{{ block "content" . }}Default content{{ end }}
<!-- Child template -->
{{ define "content" }}Overridden content{{ end }}
Summary
Template inheritance in Gin provides a powerful way to create modular, maintainable templates for your web applications. By using base layouts and child templates, you can:
- Keep your HTML code DRY and maintainable
- Create consistent layouts across your site
- Override specific sections as needed in different pages
- Create different layouts for different sections of your site
The key concepts to remember are:
- Use
{{ block "name" . }}
to define sections that can be overridden - Use
{{ define "name" }}
in child templates to override those sections - Use
{{ template "path/to/template.html" . }}
to include templates
With these techniques, you can create sophisticated template structures that are both flexible and maintainable.
Additional Resources and Exercises
Additional Resources
Exercises
-
Basic Layout: Create a base layout with header, footer, and content blocks. Then create three different page templates that inherit from this layout.
-
Multi-Layout Site: Create a site with two different layouts - one for public pages and one for a dashboard area.
-
Component Library: Create a collection of reusable components (cards, alerts, forms) that can be included in various templates.
-
Blog Theme: Implement a complete blog theme with home, archive, single post, and about page templates using template inheritance.
-
Advanced: Create a layout system that allows templates to specify which layout they want to use, rather than hardcoding it in your Go code.
By mastering template inheritance in Gin, you'll be able to create elegant, maintainable template structures for your Go web applications, resulting in more organized code and a better development experience.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)