Membangun Web App E-Commerce dengan Go: #3 - Routing & Tampilan HTML

Membangun Web App E-Commerce dengan Go: #3 - Routing & Tampilan HTML

Aplikasi kita mulai terlihat! Di bagian ini kita akan membangun sistem routing yang rapi menggunakan 'chi', dan belajar cara merender data dari Go ke template HTML.

Penulis: Novian Hidayat
Tanggal: 1 Juli 2025
Level: beginner

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:

  1. Routing yang Terstruktur: Mengganti router bawaan Go dengan chi, sebuah router pihak ketiga yang ringan namun sangat powerful.
  2. Server-Side Rendering yang Andal: Menggunakan paket html/template dan fitur embed 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!

Hasil Render HTML

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

Diskusi

Tutorial Lainnya