Sampai saat ini kita hanya membahas tentang membuat response menggunakan String dan juga static file. Pada kenyataan nya, saat kita membuat web kita pasti akan membuat halaman yang dinamis, bisa berubah-ubah sesuai data yang diakses oleh user. Di golang terdapat fitur HTML Template, yaitu fitur template yang bisa kita gunakan untuk membuat HTML yang dinamis.
HTML Template
Fitur HTML Template terdapat di package html/template, sebelum menggunakan template, kita perlu terlebih dahulu membuat template nya. Template bisa berupa file atau string. Bagian dinamis pada HTML Template adalah bagian yang menggunakan tanda {{ }}
Membuat HTML Template
Saat membuat template dengan string kita perlu memberitahu nama template nya, jika menggunakan file maka secara otomatis nama file nya akan digunakan sebagai nama template. Dan untuk membuat text template, cukup buat text html, dan untuk konten yang dinamis kita bisa gunakan tanda {{.}}
, contoh : <html><body>{{.}}</body></html>
package belajar_golang_web
import (
"fmt"
"html/template"
"io"
"net/http"
"net/http/httptest"
"testing"
)
func SimpleHtml(writer http.ResponseWriter, request *http.Request) {
templateText := `<html><body>{{.}}</body></html>`
t := template.Must(template.New("SIMPLE").Parse(templateText))
t.ExecuteTemplate(writer, "SIMPLE", "Hello HTML Template")
}
func TestSimpleHTML(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
SimpleHtml(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Template dari file
Selain membuat template dari string, kita juga bisa membuat template langsung dari file, hal ini mempermudah kita karena bisa langsung membuat file html. Saat membuat template menggunakan file, secara otomatis nama file akan menjadi nama templatenya, misal jika kita punya file simple.html
, maka nama templatenya adalah simple.html
. Dan di golang biasanya kita menggunakan ekstensi .gohtml
.
package belajar_golang_web
import (
"fmt"
"html/template"
"io"
"net/http"
"net/http/httptest"
"testing"
)
func SimpleHtmlFile(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles("./templates/simple.gohtml"))
t.ExecuteTemplate(writer, "simple.gohtml", "Hello HTML Template")
}
func TestSimpleHTMLFile(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
SimpleHtmlFile(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Template directory
Kadang jarang sekali kita menyebutkan file template satu persatu. Alangkah baiknya untuk template kita simpan di satu directory. Golang template mendukung proses load template dari directory. Hal ini memudahkan kita, sehingga tidak perlu meneyebutkan nama file nya satu persatu.
package belajar_golang_web
import (
"fmt"
"html/template"
"io"
"net/http"
"net/http/httptest"
"testing"
)
func TemplateDirectory(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseGlob("./templates/*.gohtml"))
t.ExecuteTemplate(writer, "simple.gohtml", "Hello HTML Template")
}
func TestTemplateDirectory(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateDirectory(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Template dari golang embed
Karena saat ini sudah ada golang embed, jadi di rekomendasikan menggunakan golang embed untuk menyimpan data template. Menggunakan golang embed menjadikan kita tidak perlu meng-copy template file lagi, karena sudah di embed di dalam distribution file.
package belajar_golang_web
import (
"embed"
"fmt"
"html/template"
"io"
"net/http"
"net/http/httptest"
"testing"
)
//go:embed templates/*.gohtml
var templates embed.FS
func TemplateEmbed(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFS(templates, "templates/*.gohtml"))
t.ExecuteTemplate(writer, "simple.gohtml", "Hello HTML Template")
}
func TestTemplateEmbed(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateEmbed(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Template Data
Saat kita membuat template, kadang kita ingin menambahkan banyak data dinamis. Hal ini bisa kita lakukan dengan cara menggunakan data struct atau map. Namun perlu dilakukan perubahan di dalam text templatenya, kita perlu memberi tahu Field atau Key mana yang akan kita gunakan untuk mengisi data dinamis di template. Contohnya seperti {{.NamaField}}
Buatlah file baru di dalam templates dan beri nama name.gohtml
kemudian isikan baris kode berikut
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
</head>
<body>
<h1>Hello {{.Name}}</h1>
</body>
</html>

Setelah membuat template, selanjutnya buta lagi file baru pada root project dengan nama template_data_test.go
kemudian masukkan baris kode berikut
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
func TemplateDataMap(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles("./templates/name.gohtml"))
t.ExecuteTemplate(writer, "name.gohtml", map[string]interface{}{
"Title": "Template Data Map",
"Name": "Rendy",
})
}
func TestTemplateDataMap(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateDataMap(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Pada percobaan diatas kita mencoma membutat template data dengan map, selanjutnya kita akan mencoba membuat template data dengan struct
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
type Page struct {
Title, Name string
}
func TemplateDataStruct(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles("./templates/name.gohtml"))
t.ExecuteTemplate(writer, "name.gohtml", Page{
Title: "Template Data Struct",
Name: "Rendy",
})
}
func TestTemplateDataStruct(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateDataStruct(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Apabila anda memiliki kebutuhan untuk mengirim nested data, maka pada bagian template anda dapat menggunakan {{.NamaField.NestedField}}
contoh nya seperti tag h2 berikut
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
</head>
<body>
<h1>Hello {{.Name}}</h1>
<h2>{{.Address.Street}}</h2>
</body>
</html>
Kemudian pada bagian kode program nya cukup buat nested struct atau map seperti biasanya
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
type Address struct {
Street string
}
type Page struct {
Title, Name string
Address Address
}
func TemplateDataStruct(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles("./templates/name.gohtml"))
t.ExecuteTemplate(writer, "name.gohtml", Page{
Title: "Template Data Struct",
Name: "Rendy",
Address: Address{
Street: "JL. Belum Ada",
},
})
}
func TestTemplateDataStruct(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateDataStruct(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}
func TemplateDataMap(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles("./templates/name.gohtml"))
t.ExecuteTemplate(writer, "name.gohtml", map[string]interface{}{
"Title": "Template Data Map",
"Name": "Rendy",
"Address": map[string]interface{}{
"Street": "JL. Belum Ada",
},
})
}
func TestTemplateDataMap(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateDataMap(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Template Action
Golang template sebenarnya mendukung perintah action, seperti percabangan, perulangan, dan lain-lain.
If else
{{if.Value}} T1 {{end}}
, jika Value tidak kosong, maka T1 akan di eksekusi, jika kosong, tidak ada yang dieksekusi{{if.Value}} T1 {{else}} T2 {{end}}
, jika Value tidak kosong maka T1 akan di eksekusi, jika kosong maka T2 yang akan di eksekusi{{if.Value1}} T1 {{else if.Value2}} T2 {{else}} T3 {{end}}
, jika Value1 tidak kosong maka T1 akan di eksekusi, jika Value2 tidak kosong maka T2 akan di eksekusi, jika tidak semua maka T3 akan di eksekusi.
Buatlah file template baru dan masukkan baris kode berikut
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
</head>
<body>
{{if .Name}}
<h1>Hello {{.Name}}</h1>
{{else}}
<h1>Hello</h1>
{{end}}
</body>
</html>

Selanjutnya buat file baru di root project dengan nama template_action_test.go
dan masukkan baris kode berikut
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
func TemplateActionIf(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles("./templates/if.gohtml"))
t.ExecuteTemplate(writer, "if.gohtml", Page{
Title: "Template Data Struct",
Name: "Rendy",
})
}
func TestTemplateActionIf(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateActionIf(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Pada kode program diatas, jika kita mengirim data Name
pada struct maka template akan mengeksekusi kode <h1>Hello {{.Name}}</h1>
jika tidak maka yang akan di eksekusi adalah <h1>Hello</h1>
Operator perbandingan
Golang template juga mendukung operator perbandingan, ini cocok ketika butuh melakukan perbandingan number di if statement, berikut adalah operator nya
Operator | Keterangan |
---|---|
eq | arg1 == arg2 |
ne | arg1 != arg2 |
lt | arg1 < arg2 |
le | arg1 <= arg2 |
gt | arg1 > arg1 |
ge | arg2 >= arg2 |
Buatlah file template baru dengan nama comparator.gohtml
dan isikan baris kode berikut
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
</head>
<body>
{{if ge .FinalValue 80}}
<h1>Good</h1>
{{else if ge .FinalValue 60}}
<h1>Nice Try</h1>
{{else}}
<h1>Try again</h1>
{{end}}
</body>
</html>

Jika di perhatikan, operator nya berada di depan, kenapa hal ini bisa terjadi? Hal ini dikarenakan, sebenarnya operator perbandingan tersebut adalah sebuah function. Jadi saat kita menggunakan {{eq First Second}}
, sebenarnya dia akan memanggil function eq
dengan parameter First dan Second : eq(First, Second)
.
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
func TemplateActionOperator(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles("./templates/comparator.gohtml"))
t.ExecuteTemplate(writer, "comparator.gohtml", map[string]interface{}{
"FinalValue": 90,
})
}
func TestTemplateActionOperator(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateActionOperator(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Template action range
Range digunakan untuk iterasi data template, di golang template tidak ada perulangan seperti biasa, contohnya menggunakan for itu tidak bisa dilakukan pada golang template. Yang bisa kita lakukan adalah menggunakan range untuk megiterasi tiap data array, slice, map atau channel. Contohnya seperti berikut
{{range $index, $element := .Value}} T1 {{end}}
, jika Value memiliki data, maka T1 akan di eksekusi sebanyak element value, dan kita bisa menggunakan $index
untuk mengakses index dan $element
untuk mengakses element.
{{range $index, $element := .Value}} T1 {{else}} T2 {{end}}
, sama seperti sebelumnya, namun jika Value tidak memiliki element apapun, maka T2 akan di eksekusi.
Buatlah template baru dengan nama range.gohtml
dan masukkan baris kode di bawah ini
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
</head>
<body>
<h1>Daftar hobi :</h1>
{{range $index, $element := .Hobbies}}
<h2>{{$element}}</h2>
{{else}}
<h1>Anda tidak punya hobi</h1>
{{end}}
</body>
</html>

Jika sudah dibuat template nya, selanjutnya seperti biasa kita akan membuat handler dan unit test nya.
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
func TemplateActionRange(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles("./templates/range.gohtml"))
t.ExecuteTemplate(writer, "range.gohtml", map[string]interface{}{
"Title": "Template action range",
"Hobbies": []string{
"Game", "Read", "Code",
},
})
}
func TestTemplateActionRange(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateActionRange(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Template action with
Kadang kita sering menggunakan nested struct, jika menggunakan template kita bisa mengakses nya menggunakan .Value.NestedValue
.
Di template terdapat action with, yang bisa digunakan mengubah scope dot menjadi object yang kita mau. Contohnya :
{{with .Value}} T1 {{end}}
, jika value tidak kosong, di T1 semua dot akan merefer ke value.{{with .Value}} T1 {{else}} T2 {{end}}
, sama seperti sebelumnya, namun jika value kosong, maka T2 yang akan di eksekusi.
Untuk mencobanya, buatlah file template baru dengan nama address.gohtml
dan seperti biasa masukkan baris kode berikut
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
</head>
<body>
<h1>Name : {{.Name}}</h1>
{{with .Address}}
<h1>Address Street : {{.Street}}</h1>
<h1>Address City : {{.City}}</h1>
{{end}}
</body>
</html>

Setelah membuat template, selanjutnya buat handler dan unit test dengan baris kode berikut
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
func TemplateActionWith(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles("./templates/address.gohtml"))
t.ExecuteTemplate(writer, "address.gohtml", map[string]interface{}{
"Title": "Template action with",
"Name": "Rendy",
"Address": map[string]interface{}{
"Street": "JL. Belum Ada",
"City": "Malang",
},
})
}
func TestTemplateActionWith(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateActionWith(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Comment
Template juga mendukung komentar, komentar secara otomatis akan hilang ketika template text di parsing. Untuk membuat komentarsangat sederhana, kita bisa gunakan {{/* Contoh komentar */}}
.
Template Layout
Saat membuat halaman website, kadang ada beberapa bagian yang selalu sama, misal header dan footer. Best practice nya jika terdapat bagian yang selalu sama disarankan utnuk disimpan pada template terpisah, agar bisa digunakan di template lain. Untuk melakukan import template kita bisa gunakan seperti berikut :
{{template "nama"}}
, artinya kita akan meng-import template “nama” tanpa memberikan data apapun{{template "nama" .Value}}
, artinya kita akan meng-import template “nama” dengan memberikan data Value
Contohnya kita akan memisahkan antara header, body dan footer menjadi template terpisah, karena header dan footer biasanya selalu sama dan tidak berubah-ubah. Jadi kita hanya perlu fokus pada body yang bisa berubah ubah isi konten nya.
Pertama buatlah file template baru yaitu header.gohtml
dan isikan baris kode berikut
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
</head>
<body>
Selanjutnya buat juga file template untuk footer footer.gohtml
dan isinya cukup seperti berikut
</body>
</html>
Kemudian untuk bagian konten nya, buatlah file template baru dengan nama layout.gohtml
dan isinya seperti berikut
{{template "header.gohtml" .}}
<h1>Hello {{.Name}}</h1>
{{template "footer.gohtml"}}

Perhatikan pada baris ke 1 kita melakukan import template header.gohtml
dan mengirimkan semua data ke template tersebut, oleh karena itu kita menggunakan .
dan untuk template footer.gohtml
kita tidak butuh mengirim data apapun, jadi kita tidak menggunakan .
atau .Value
Selanjutnya untuk kode program template layout nya, buat lah file baru pada root project terlebih dahulu dengan nama template_layout_test.go
dan isikan baris kode berikut
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
func TemplateLayout(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles(
"./templates/header.gohtml",
"./templates/footer.gohtml",
"./templates/layout.gohtml",
))
t.ExecuteTemplate(writer, "layout.gohtml", map[string]interface{}{
"Title": "Template layout",
"Name": "Rendy",
})
}
func TestTemplateLayout(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateLayout(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Saat menggunakan template layout, kita harus mengimport semua layout nya, seperti pada baris 13. Pada kenyataannya nanti kita tidak akan menggunakan ParseFiles
karena harus menuliskan semua layout nya, biasanya kita akan menggunkaan glob atau embed seperti yang sudah di bahas sebelumnya.
Template name
Saat kita membuat template dari file, secara otomatis nama file nya akan menjadi nama template. Namun jika kita ingin mengubah nama template nya, kita juga bisa melakukannya dengan menggunakan perintah {{define "nama"}} TEMPLATE {{end}}
, artinya kita membuat template dengan nama “nama”.
Contohnya seperti berikut, buka kembali file layout.gohtml
dan ubah menjadi
{{define "layout"}}
{{template "header.gohtml" .}}
<h1>Hello {{.Name}}</h1>
{{template "footer.gohtml"}}
{{end}}

Kemudian untuk pemanggilan template nya kita tidak perlu menuliskan full seperti layout.gohtml
tapi hanya perlu layout
saja seperti berikut.
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
func TemplateLayout(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.ParseFiles(
"./templates/header.gohtml",
"./templates/footer.gohtml",
"./templates/layout.gohtml",
))
t.ExecuteTemplate(writer, "layout", map[string]interface{}{
"Title": "Template layout",
"Name": "Rendy",
})
}
func TestTemplateLayout(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateLayout(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Template function
Selain mengakses field dalam template , function juga bisa diakses, contohnya jika kita memiliki function di dalam struct. Cara mengakses function sama seperti field, namun jika function tersebut memiliki parameter kita bisa tambahkan parameter ketika memanggil function di templatenya. Contoh :
{{.FunctionName}}
, memanggil filed FunctionName atau functionFunctionName()
{{.FunctionName "Rendy" "Wijaya"}}
, memanggil functionFunctionName("Rendy", "Wijaya")
Untuk mencoba nya, buatlah file baru dengan nama template_function_test.go
dan masukkan baris kode berikut
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
)
type MyPage struct {
Name string
}
func (mypage MyPage) SayHello(name string) string {
return "Hello " + name + ", My name is " + mypage.Name
}
func TemplateFunction(writer http.ResponseWriter, request *http.Request) {
t := template.Must(template.New("FUNCTION").Parse(`{{.SayHello "Budi"}}`))
t.ExecuteTemplate(writer, "FUNCTION", MyPage{
Name: "Rendy",
})
}
func TestTemplateFunction(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateFunction(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Golang template global function
Golang template memiliki beberapa global function, global function adalah function yang bisa digunakan secara langsung, tanpa menggunakan template data. Berikut adalah beberapa global function di golang template : https://github.com/golang/go/blob/master/src/text/template/funcs.go
Selain global function bawaan golang, kita juga bisa menambahkan global function buatan kita sendiri. Untuk menambah global function, kita bisa menggunakan method Funcs
pada template. Tetapi perlu di ingat, bahwa menambahkan global function harus dilakukan sebelum parsing template.
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"text/template"
)
func TemplateCreateGLobal(writer http.ResponseWriter, request *http.Request) {
t := template.New("FUNCTION")
t = t.Funcs(map[string]interface{}{
"upper": func(value string) string {
return strings.ToUpper(value)
},
})
t = template.Must(t.Parse(`{{upper .Name}}`))
t.ExecuteTemplate(writer, "FUNCTION", MyPage{
Name: "Rendy",
})
}
func TestTemplateCreateGlobal(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateCreateGLobal(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Golang template function piplines
Golang template mendukung function piplines, artinya hasil dari function bisa dikirim ke function berikutnya. Untuk menggunakan function pipelines, kita bisa menggunakan tanda |
, misal : {{ sayHello .Name | upper}}
, artinya akan memanggil global function sayHello(Name)
hasil dari sayHello(name)
akan dikirim ke function upper(hasil)
. Dan di golang template kita bisa menambahkan lebih dari satu function pipelines.
package belajar_golang_web
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"text/template"
)
func TemplateCreateGLobalPipelines(writer http.ResponseWriter, request *http.Request) {
t := template.New("FUNCTION")
t = t.Funcs(map[string]interface{}{
"sayHello": func(name string) string {
return "Hello " + name
},
"upper": func(value string) string {
return strings.ToUpper(value)
},
})
t = template.Must(t.Parse(`{{sayHello .Name | upper}}`))
t.ExecuteTemplate(writer, "FUNCTION", MyPage{
Name: "Rendy",
})
}
func TestTemplateCreateGlobalPipelines(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateCreateGLobalPipelines(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Template parshing
Kode-kode diatas yang sudah kita praktekan sebenarnya tidak efisien, hal ini dikarenakan setiap handler dipanggil, kita selalu melakukan parsing ulang templatenya. Idealnya template hanya melakukan parsing satu kali diawali ketika aplikasinya berjalan. Selanjutnya data template akan di caching(disimpan di memory), sehingga kita tidak perlu melakukan parsing lagi. Hal ini akan membuat web kita semakin cepat.
package belajar_golang_web
import (
"embed"
"fmt"
"html/template"
"io"
"net/http"
"net/http/httptest"
"testing"
)
//go:embed templates/*.gohtml
var templates embed.FS
var myTemplates = template.Must(template.ParseFS(templates, "templates/*.gohtml"))
func TemplateCaching(writer http.ResponseWriter, request *http.Request) {
myTemplates.ExecuteTemplate(writer, "simple.gohtml", "Hello Template Caching")
}
func TestTemplateCaching(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "localhost:8080", nil)
recorder := httptest.NewRecorder()
TemplateCaching(recorder, request)
body, _ := io.ReadAll(recorder.Result().Body)
fmt.Println(string(body))
}

Sebenar nya jika kita eksekusi kode program nya maka hasilnya akan sama saja, yang membedakan adalah karena kita melakukan parsing template nya di global maka, parsing tersebut akan dijalankan hanya satu kali saat aplikasi pertama berjalan dan hasilnya akan di simpan di memory. Dengan demikian saat handler dijalankan kita tidak perlu parsing kembali karena sudah tersimpan di memory dan tinggal di pakai saja.
Penutup
Pada artikel kali ini kita telah belajar tentang template pada golang web. Dan pada artikel selanjutnya saya akan membahas XSS Cross Site Scripting pada golang web.