DEV Community

Cover image for Go Gin Templates: Break Them Down
Ivan Pesenti
Ivan Pesenti

Posted on

Go Gin Templates: Break Them Down

Today Problem

Welcome back, folks! Today's problem ❌ is how to embed one HTML template into another. In this way, we can foster the reusability of the code and make it less redundant. Let's pretend to have two HTML pages to render. These are identical unless for the middle part of the page. Why don't we have a header, a footer, and two bodies with only the specific page's information? You already figured it out since it's a typical use case. It's worth to cover. Let's see how we can achieve it with Go and the Gin web framework.

What is HTML Templating?

Before getting straight into the code, let's talk a little about HTML templating. It allows you to blend HTML tags with values provided later. Gin manages the HTML templating by empowering the usage of the package html/template that belongs to Go's Standard Library. You can read more here. Now, let's start presenting the solution 🚀.

The solution

First, let's look at the HTML templates, and then we'll see how to invoke them in the code and how to run some tests. Let's present the final HTML pages we'd like to have:

Page 1 Page 2

As you can see, the headers and footers are identical. However, the "core" parts differ based on the requested page.

HTML Templates

All the templates lay in the templates folder. The files are:

  • header.tmpl
  • page1.tmpl
  • page2.tmpl
  • footer.tmpl

First, let's cover the static ones.

The header.tmpl file

Its content is:

{{define "header" }}
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>{{ .title }}</title>
</head>
<body>
 <h1>BASELINE</h1>
 <div id="content">
{{end}}
Enter fullscreen mode Exit fullscreen mode

Here, there are a couple of things to pay attention to. All the template's code resides between the start and end definitions of the template. In this case, the boundaries are {{define "header" }} and {{end}}. Then, we use a parameter called title within the <title> tag that can be assigned whenever we invoke this template from the child templates. More on that later.

The footer.tmpl file

Here, the content is even easier:

{{define "footer"}}
</div>
<p>FooterInfo</p>
</body>
</html>
{{end}}
Enter fullscreen mode Exit fullscreen mode

The footer template is the final part of all the pages that will always be consistent.

The page1.tmpl file

Now, the spicy part 🌶️. The HTML part that changes lives in the page1.tmpl file. The content is as follows:

{{template "header" . }}
<h2>Welcome to Page 1</h2>
<p>This is the content of Page 1.</p>
{{template "footer"}}
Enter fullscreen mode Exit fullscreen mode

Here, there are a couple of things worth mentioning. First, we add the reference to the header template by using the line {{template "header" . }}.

There's a 1:1 relationship between the names used in the header.tmpl and page1.tmpl files (e.g. in both we used header).

The . means to pass along over the pipeline all the parameters. Thanks to this way, we can pass the value Page 1 IS HERE to the page1.tmpl file that, in turn, will pass it to the header template that is the one using it.

Then, we type the specific content for this page and reference the footer template to spread consistency around 🎇.

Please be aware that I skipped the code for the page2.tmpl file since it's almost identical to the one just seen.

Now, let's take a look at the Go code.

The Go Code

Here, we have to tie everything up and be able to respond to the HTTP endpoints. For the sake of the demo, the whole code is in the main.go file. First, let me present the code, and then I'll walk you through all the relevant sections of it.

package main

import (
 "fmt"
 "net/http"
 "os"

 "github.com/gin-gonic/gin"
)

func main() {
 gin.SetMode(gin.DebugMode)
 r := gin.Default()

 // load templates
 r.LoadHTMLGlob("templates/*.tmpl")

 r.GET("/page1", func(c *gin.Context) {
 c.HTML(http.StatusOK, "page1.tmpl", gin.H{
   // this value goes to the "header.tmpl" file
   "title": "Page 1 IS HERE",
  })
 })

 r.GET("/page2", func(c *gin.Context) {
 c.HTML(http.StatusOK, "page2.tmpl", gin.H{
   "title": "Page 2 IS HERE",
  })
 })

 if err := r.Run(":8080"); err != nil {
 fmt.Fprintf(os.Stderr, "cannot run the srv: %v\n", err)
 os.Exit(1)
 }
}
Enter fullscreen mode Exit fullscreen mode

The first noticeable thing is the r.LoadHTMLGlob("templates/*.tmpl") call. It loads all the matching templates in a specific directory. For completeness, you could also have used r.LoadHTMLFiles() to specify the files you want to load.

In real-life projects, do not hard-code the templates' filesystem path in your code.

Then, we instrument the router to answer GET HTTP requests that hit the /page1 and /page2 addresses. Within the HandlerFunc body, we used the method c.HTML that allows us to set:

  1. The HTTP Response Status Code
  2. The template to use for this kind of request
  3. Optional parameters we may want to pass to the template to make it more dynamic

We used the type gin.H for parameters to have great flexibility since it's based on the underlying type map[string]any.
Finally, we launch our server and catch 🔎 the eventual error.

Give It a Try

Now, let's make sure that everything works as expected. Issue go run ., and checks that the server started without problems. To test it out, we're going to use the curl command (you may need to install it on your machine. More on it here):

curl http://localhost:8080/page1

The output printed in the shell should be this one:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Page 1 IS HERE</title>
</head>
<body>
    <h1>BASELINE</h1>
    <div id="content">

<h2>Welcome to Page 1</h2>
<p>This is the content of Page 1.</p>

</div>
<p>FooterInfo</p>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

You can also try it directly from the web browser of your choice. It doesn't matter, you'll have the same result.

That's a Wrap

This blog post is not exhaustive about HTML templating. I encourage you to have a look at these resources:

Any feedback is highly appreciated.

Before leaving, I strongly invite you to reach out if you are interested in some topics and want me to address them. I swear I shortlist them and do my best to deliver helpful content.

Thank you very much for the support, and see you in the next one 👋

Top comments (0)