Selamat datang di bagian ketiga dari seri e-commerce Go kita! Sejauh ini, kita sudah punya server web yang terhubung ke database. Namun, aplikasi kita masih menampilkan teks sederhana. Sudah saatnya kita memberikan “wajah” pada aplikasi kita dengan HTML.
Di artikel ini, kita akan fokus pada dua konsep inti:
- Routing yang Terstruktur: Mengganti router bawaan Go dengan
chi
, sebuah router pihak ketiga yang ringan namun sangat powerful. - Server-Side Rendering yang Andal: Menggunakan paket
html/template
dan fiturembed
dari Go untuk mem-parsing dan menampilkan file HTML secara efisien dan portabel.
Mari kita ubah aplikasi konsol kita menjadi aplikasi web yang sesungguhnya!
Langkah 1: Mengganti Router dengan chi
Router bawaan net/http
sudah cukup untuk aplikasi sederhana, tetapi untuk proyek yang lebih besar, kita butuh sesuatu yang lebih fleksibel, terutama untuk menangani parameter URL (seperti /products/123
) dan middleware. chi
adalah pilihan yang sangat populer.
Instal chi
melalui terminal di root proyek Anda:
go get github.com/go-chi/chi/v5
Langkah 2: Refaktor main.go
dan Struktur Handler
Aplikasi kita akan semakin kompleks. Menulis semua logika di main()
akan membuatnya berantakan. Kita akan memisahkan definisi rute dan logika penanganannya (handler).
Buat dua direktori baru di dalam cmd/web
:
mkdir -p cmd/web/handler cmd/web/router
handler/
: Akan berisi fungsi-fungsi yang menangani request (misal:ShowHomePage
).router/
: Akan berisi definisi rute-rute aplikasi kita.
Sekarang, mari kita buat file-file tersebut.
1. Buat handler/home.go
// cmd/web/handler/home.go
package handler
import (
"net/http"
)
func ShowHomePage(w http.ResponseWriter, r *http.Request) {
// Untuk sementara, kita isi dengan teks sederhana.
// Nanti akan kita ganti dengan render template.
w.Write([]byte("Ini adalah halaman utama dari file handler!"))
}
2. Buat router/router.go
// cmd/web/router/router.go
package router
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/kodekilat/go-ecommerce/cmd/web/handler" // Ganti dengan path modul Anda
)
func New() http.Handler {
r := chi.NewRouter()
// Middleware dasar
r.Use(middleware.Logger) // Mencatat log setiap request
r.Use(middleware.Recoverer) // Memulihkan dari panic
// Definisikan rute
r.Get("/", handler.ShowHomePage)
return r
}
3. Perbarui cmd/web/main.go
main.go
kita menjadi jauh lebih bersih. Tugas utamanya hanya inisialisasi dan menjalankan server.
// cmd/web/main.go
package main
import (
"log"
"net/http"
"github.com/kodekilat/go-ecommerce/cmd/web/router" // Ganti dengan path modul Anda
"github.com/kodekilat/go-ecommerce/internal/database"
)
func main() {
db, err := database.NewConnection()
if err != nil {
log.Fatalf("Tidak dapat terhubung ke database: %v", err)
}
defer db.Close()
appRouter := router.New()
log.Println("Memulai server di http://localhost:8080")
err = http.ListenAndServe(":8080", appRouter)
if err != nil {
log.Fatal(err)
}
}
Pastikan Anda mengganti github.com/kodekilat/go-ecommerce
dengan path modul Anda sendiri.
Jalankan aplikasi (go run ./cmd/web/main.go
). Buka http://localhost:8080
dan Anda akan melihat pesan dari handler. Perhatikan juga terminal Anda, chi
akan mencatat setiap request yang masuk!
Langkah 3: Membuat Template HTML Pertama
Saatnya mengucapkan selamat tinggal pada w.Write
. Kita akan membuat file HTML sungguhan.
Buat file baru di web/templates/home.page.html
:
<!-- web/templates/home.page.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Toko Online Go</title>
<style>
body { font-family: sans-serif; background-color: #f4f4f9; color: #333; text-align: center; margin-top: 50px; }
h1 { color: #2c3e50; }
p { color: #7f8c8d; }
ul { list-style: none; padding: 0; }
li { background: white; margin: 0.5rem auto; padding: 0.5rem; border-radius: 4px; max-width: 300px; }
</style>
</head>
<body>
<h1>{{.PageTitle}}</h1>
<p>{{.WelcomeMessage}}</p>
<h2>Daftar Produk (Dummy)</h2>
<ul>
{{range .Products}}
<li>{{.Name}} - Rp{{.Price}}</li>
{{else}}
<li>Belum ada produk untuk ditampilkan.</li>
{{end}}
</ul>
</body>
</html>
Perhatikan sintaks {{.PageTitle}}
dan {{range .Products}}
. Ini adalah sintaks template Go yang akan kita isi dengan data dinamis.
Langkah 4: Render Template dengan Andal Menggunakan embed
Membaca file dari disk bisa menyebabkan masalah path (seperti yang mungkin kita alami!). Go modern (versi 1.16+) menyediakan solusi yang elegan: paket embed
. Fitur ini memungkinkan kita untuk “menanamkan” file-file template langsung ke dalam file binary aplikasi saat kompilasi.
Keuntungannya:
- Portabel: Aplikasi kita tidak lagi bergantung pada file eksternal. Anda bisa mengirim satu file binary dan itu akan berjalan di mana saja.
- Andal: Tidak akan ada lagi error “file not found” karena path yang salah.
- Efisien: Template bisa di-cache di memori saat aplikasi dimulai, bukan dibaca dari disk setiap kali ada request.
Mari kita implementasikan. Buat direktori dan file baru:
mkdir -p cmd/web/view
touch cmd/web/view/template.go
Isi cmd/web/view/template.go
dengan kode final berikut:
// cmd/web/view/template.go
package view
import (
"embed"
"html/template"
"log"
"net/http"
)
//go:embed ../../web/templates
var templateDir embed.FS
// Parse semua template dari dalam direktori `templates` yang sudah di-embed.
// Path `templates/*.page.html` relatif terhadap root dari `templateDir`.
var templates = template.Must(template.ParseFS(templateDir, "templates/*.page.html"))
// Render adalah fungsi pembantu untuk mengeksekusi template yang sudah di-cache.
func Render(w http.ResponseWriter, tmplName string, data any) {
err := templates.ExecuteTemplate(w, tmplName, data)
if err != nil {
log.Printf("Error executing template %s: %v", tmplName, err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
Langkah 5: Menggunakan Render di Handler
Terakhir, mari kita modifikasi handler/home.go
untuk menggunakan fungsi Render
yang baru kita buat.
// cmd/web/handler/home.go
package handler
import (
"net/http"
"github.com/kodekilat/go-ecommerce/cmd/web/view" // Ganti dengan path modul Anda
)
// Definisikan struktur untuk data produk dummy
type Product struct {
Name string
Price int
}
func ShowHomePage(w http.ResponseWriter, r *http.Request) {
// Siapkan data yang akan dikirim ke template
pageData := struct {
PageTitle string
WelcomeMessage string
Products []Product
}{
PageTitle: "Selamat Datang di Toko Kami!",
WelcomeMessage: "Temukan produk terbaik hanya di sini.",
Products: []Product{
{Name: "Buku Go Keren", Price: 150000},
{Name: "Stiker Gopher", Price: 25000},
{Name: "Mug PostgreSQL", Price: 75000},
},
}
// Panggil fungsi render dengan nama file template
view.Render(w, "home.page.html", pageData)
}
Jalankan kembali aplikasi Anda (go run ./cmd/web/main.go
) dan segarkan browser. Anda akan melihat halaman HTML yang cantik dengan data yang diisi secara dinamis oleh Go!
Penutup
Luar biasa! Aplikasi kita sekarang memiliki struktur routing yang solid dan mampu menampilkan halaman web dinamis dengan cara yang modern dan andal. Kita telah memisahkan antara logika, routing, dan tampilan, yang merupakan praktik fundamental dalam pengembangan web.
Di bagian selanjutnya, kita akan masuk ke salah satu fitur paling penting: Autentikasi Pengguna. Kita akan membuat sistem registrasi dan login dari nol.
Tetap semangat dan sampai jumpa di bagian keempat!
Untuk referensi, Anda dapat melihat kode final dari tutorial ini di repositori GitHub berikut: Source code lengkap