Introduction Gin HTML template and how integration with bootstrap

Gin is going web framework which include server-side render template, we can transfer variable into a template and render by double braces {{ … }}.

In this article, We will introduce how to use gin template, finally will include how to integrate bootstrap with gin HTML template.

Getting start

First, we need to prepare a gin framework and. Fellowing content you also can reference with gin official Github tutorial.

Notice: I have change the loadHTMLGlob rule from * to *.tmpl

func main() {
	router := gin.Default()
	router.LoadHTMLGlob("templates/*.tmpl")
	//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
	router.GET("/index", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"title": "Main website",
		})
	})
	router.Run(":8080")
}

Create a templates folder and index.tmpl file

<html>
	<h1>
		{{ .title }}
	</h1>
</html>

How to transfer data into template

In fellowing will showing how to transfer data into the template.

1. The single variable of non-struct types

When transfer value of non-struct types,

c.HTML(http.StatusOK, "index.tmpl", gin.H{
	"FielName": "Main website",
})

We can just use {{.FielName}} action

2. Range loop data

When transferring a slice, can using the range action for loop data in gin.

data := fetchUsers()
c.HTML(http.StatusOK, "index.tmpl", gin.H{
	"Users": data,
})

func fetchUsers() []string {
    data := []string{"Adam", "Brown", "Cayla"}
    return data
}

template:

{{ range .Users }}
    {{ . }}
{{ end }}

3. Render loop data for slice data with struct

type Users struct {
	Name string

	Phone string

	Age int
}
func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/*.tmpl")
    userData := []Users{
        {Name: "Adam", Phone: "099911111", Age: 23},
        {Name: "Brown", Phone: "0933222222", Age: 22},
        {Name: "Cayla", Phone: "0933333333", Age: 18},
    }
    r.GET("/", func(c *gin.Context) {
        "userdata": userData,
    }

In templates, demo with range, when, range key value, all of three actions get same result:

{{ range .userdata }}
        Name: {{ .Name }},
        Phone: {{ .Phone }},
        Age: {{ .Age }}
{{ end }}

{{ when .userdata }}
        Name: {{ .Name }},
        Phone: {{ .Phone }},
        Age: {{ .Age }}
{{ end }}

{{ range $key, $value := .userdata }}
        {{ $key }}
        Name: {{ $value.Name }},
        Phone: {{ $value.Phone }},
        Age: {{ $value.Age }}
{{ end }}

When we using with, you also can define a variable with {{ with $x := <^>result-of-some-action<^> }}

Intergration with Bootstrap statics file

Download Bootstrap (here is using v5.0.1) to local, create an assets folder, and put the bootstrap file in assets.

Add assets static route in gin:

r.Static("/assets", "./assets")

Now you can access the bootstrap file in the assets path.

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="/assets/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous" >

    <title>{{ .title }}</title>
  </head>
  <body>
	  <h1>{{ .title }}</h1>


	  <script src="/assets/js/bootstrap.bundle.min.js"  integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
</body>

Create a reusable template in Gin

Using sub-template can separate re-usable component like header, footner, navigator…

First, create folder and file like fellowing:

|-templates
    |-globals
        |-header.tmpl
        |-footer.tmpl
    |-static
        |-(static files)
    |-users
        |-users.tmpl
    |-home
        |-index.tmpl

templates/globals/header.tmpl

{{ define "globals/header.tmpl" }}
<header>
	<h1>{{ .title }}</h1>
	<a href="/">Home</a>
	<a href="/users/">User</a>
</header>
{{ end }}

templates/globals/footer.tmpl

{{ define "globals/footer.tmpl" }}
<footer>
	<p>
	copyright by test {{ .title }}
	</p>
</footer>
{{ end }}

templates/users/users.tmpl

{{ define "users/users.tmpl" }}

{{ template "globals/header.tmpl" .}}
<p>
user index file
{{ .title }}
</p>
{{ template "globals/footer.tmpl" .}}
{{ end }}

templates/home/index.tmpl

 <body>
	  {{ template "globals/header.tmpl" .}}
	  <div>
		  CONETNETN AREA
	  </div>
	  {{ template "globals/footer.tmpl" .}}

main.go

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()
	//ping
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})

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

	//static path
	r.Static("/assets", "./assets")

	//show home
	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "home/index.tmpl", gin.H{
			"title":    "Home Page",
		})
	})

	//show user template
	r.GET("/users", func(c *gin.Context) {
		c.HTML(http.StatusOK, "users/users.tmpl", gin.H{
			"title": "Users Page",
		})
	})
	//run
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

Others

Fellowing are some issue notes:

golang “gin” is an unexported field of struct type

This error happened when unexported field is used.

type Users struct {
	id         int
	key        string
}

Use capitals for exported fields in a struct, for referring in the outside package.

type Users struct {
	Id         int
	Key        string
}