Membangun Web App E-Commerce dengan Go: #8 - Tampilan Produk & Styling dengan UnoCSS

Membangun Web App E-Commerce dengan Go: #8 - Tampilan Produk & Styling dengan UnoCSS

Bagian terakhir! Tampilkan produk Anda ke dunia. Pelajari cara mengambil data produk, membuat halaman detail, dan menata semuanya dengan UnoCSS agar terlihat profesional.

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

Selamat datang di bagian terakhir dari seri “Membangun Web App E-Commerce dengan Go”! Anda telah melakukan perjalanan luar biasa, dari server “Hello World” sederhana hingga sistem manajemen produk dengan upload gambar. Sekarang, saatnya menunjukkan hasil kerja keras kita kepada dunia.

Di bagian finale ini, kita akan fokus pada sisi frontend:

  1. Menampilkan Semua Produk di halaman utama.
  2. Membuat Halaman Detail Produk yang dinamis.
  3. Mengintegrasikan UnoCSS, sebuah utility-first CSS engine yang sangat cepat, untuk memberikan tampilan yang modern dan profesional pada aplikasi kita.

Setelah ini, Anda akan memiliki sebuah prototipe web app e-commerce yang berfungsi penuh!

Langkah 1: Menampilkan Daftar Produk di Halaman Utama

Saat ini, halaman utama kita masih menampilkan data dummy. Mari kita ganti dengan data produk asli dari database.

1. Buat Method GetAllProducts di Repository

Buka internal/repository/product_repo.go dan tambahkan method baru:

// internal/repository/product_repo.go
// ...

func (r *ProductRepository) GetAllProducts() ([]models.Product, error) {
	query := `SELECT id, name, price, image_url FROM products ORDER BY created_at DESC`
	
	rows, err := r.DB.Query(context.Background(), query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var products []models.Product
	for rows.Next() {
		var product models.Product
		err := rows.Scan(&product.ID, &product.Name, &product.Price, &product.ImageURL)
		if err != nil {
			return nil, err
		}
		products = append(products, product)
	}

	return products, nil
}

Catatan: Kita hanya mengambil kolom yang diperlukan untuk halaman daftar produk agar lebih efisien.

2. Suntikkan ProductRepository ke HomeHandler

Kita perlu memberikan akses ProductRepository ke handler halaman utama.

Pertama, ubah cmd/web/handler/home.go agar memiliki ProductRepository:

// cmd/web/handler/home.go
package handler

import (
    "log"
	"net/http"

	"github.com/kodekilat/go-ecommerce/cmd/web/view"
	"github.com/kodekilat/go-ecommerce/internal/repository"
)

type HomeHandler struct {
	ProductRepo *repository.ProductRepository
}

func (h *HomeHandler) ShowHomePage(w http.ResponseWriter, r *http.Request) {
    // Ambil semua produk dari repository
	products, err := h.ProductRepo.GetAllProducts()
	if err != nil {
		log.Printf("Gagal mengambil produk: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	pageData := struct {
		Products []models.Product
	}{
		Products: products,
	}

	view.Render(w, "home.page.html", pageData)
}

Kemudian, perbarui router.go untuk membuat instance HomeHandler dengan dependensinya:

// cmd/web/router/router.go
// ...

func New(userRepo *repository.UserRepository, productRepo *repository.ProductRepository) http.Handler {
	// ...
	authHandler := &handler.AuthHandler{UserRepo: userRepo}
	adminHandler := &handler.AdminHandler{ProductRepo: productRepo}
	homeHandler := &handler.HomeHandler{ProductRepo: productRepo} // Tambahkan ini

	r.Get("/", homeHandler.ShowHomePage) // Ubah ini

    // ... sisa rute
	return r
}

3. Perbarui Template home.page.html

Ubah web/templates/home.page.html untuk menampilkan produk asli:

<!-- web/templates/home.page.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Toko Online Go</title>
</head>
<body>
    <h1>Selamat Datang di Toko Kami!</h1>
    
    <div class="product-grid">
        {{range .Products}}
            <div class="product-card">
                <a href="/products/{{.ID}}">
                    <img src="{{.ImageURL}}" alt="{{.Name}}">
                    <h3>{{.Name}}</h3>
                    <p>Rp {{.Price}}</p>
                </a>
            </div>
        {{else}}
            <p>Belum ada produk untuk ditampilkan.</p>
        {{end}}
    </div>
</body>
</html>

Perhatikan link href="/products/{{.ID}}". Ini akan kita implementasikan selanjutnya.

Langkah 2: Membuat Halaman Detail Produk

Kita butuh halaman individual untuk setiap produk.

1. Buat Method GetProductByID di Repository Tambahkan method ini di internal/repository/product_repo.go:

// internal/repository/product_repo.go
// ...
func (r *ProductRepository) GetProductByID(id uuid.UUID) (*models.Product, error) {
	query := `SELECT id, name, description, price, stock, image_url FROM products WHERE id = $1`
	var product models.Product
	err := r.DB.QueryRow(context.Background(), query, id).Scan(&product.ID, &product.Name, &product.Description, &product.Price, &product.Stock, &product.ImageURL)
	if err != nil {
		return nil, err
	}
	return &product, nil
}

2. Buat Handler & Rute untuk Detail Produk Buat handler baru di cmd/web/handler/product.go (file baru):

// cmd/web/handler/product.go
package handler

import (
	"log"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	"github.com/kodekilat/go-ecommerce/cmd/web/view"
	"github.com/kodekilat/go-ecommerce/internal/repository"
)

type ProductHandler struct {
	ProductRepo *repository.ProductRepository
}

func (h *ProductHandler) ShowProductDetail(w http.ResponseWriter, r *http.Request) {
	productIDStr := chi.URLParam(r, "productID")
	productID, err := uuid.Parse(productIDStr)
	if err != nil {
		http.Error(w, "ID Produk tidak valid", http.StatusBadRequest)
		return
	}

	product, err := h.ProductRepo.GetProductByID(productID)
	if err != nil {
		log.Printf("Produk tidak ditemukan: %v", err)
		http.NotFound(w, r)
		return
	}

	view.Render(w, "product_detail.page.html", product)
}

Daftarkan rute baru ini di router.go:

// cmd/web/router/router.go
// ...
func New(...) http.Handler {
    // ...
	productHandler := &handler.ProductHandler{ProductRepo: productRepo} // Buat instance handler
    
    r.Get("/", homeHandler.ShowHomePage)
    r.Get("/products/{productID}", productHandler.ShowProductDetail) // Rute detail produk

    // ... sisa rute
}

3. Buat Template Detail Produk Buat file web/templates/product_detail.page.html:

<!-- web/templates/product_detail.page.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.Name}}</title>
</head>
<body>
    <img src="{{.ImageURL}}" alt="{{.Name}}" style="max-width: 400px;">
    <h1>{{.Name}}</h1>
    <p><strong>Harga:</strong> Rp {{.Price}}</p>
    <p><strong>Stok:</strong> {{.Stock}}</p>
    <hr>
    <p>{{.Description}}</p>
</body>
</html>

Langkah 3: Styling dengan UnoCSS

Aplikasi kita berfungsi, tapi tampilannya masih sangat dasar. Mari kita percantik dengan UnoCSS.

1. Setup UnoCSS

UnoCSS adalah build tool. Kita perlu npm (Node.js) untuk menginstalnya. Jika belum punya, instal Node.js terlebih dahulu. Di root proyek Anda, inisialisasi proyek Node.js dan instal UnoCSS:

npm init -y
npm install -D unocss

2. Konfigurasi UnoCSS Buat file uno.config.js di root proyek:

// uno.config.js
import { defineConfig } from 'unocss'

export default defineConfig({
  // ... Konfigurasi UnoCSS
  // Untuk saat ini, kita bisa biarkan kosong untuk menggunakan preset default
})

Buat file CSS input, misalnya web/static/css/input.css:

/* web/static/css/input.css */
@unocss;

Tambahkan script ke package.json Anda untuk menjalankan UnoCSS:

// package.json
"scripts": {
  "css:watch": "unocss --watch \"./web/templates/**/*.html\" --out-file \"./web/static/css/style.css\"",
  "css:build": "unocss \"./web/templates/**/*.html\" --out-file \"./web/static/css/style.css\""
},

3. Jalankan UnoCSS & Sajikan File Statis Jalankan UnoCSS dalam mode watch di terminal terpisah:

npm run css:watch

Ini akan memindai semua file HTML di web/templates, mencari class utility, dan secara otomatis menghasilkan file web/static/css/style.css.

Sekarang, kita perlu “menyajikan” file style.css ini. Gunakan file server bawaan chi. Buat folder web/static/css jika belum ada. Di router.go, tambahkan handler untuk file statis:

// cmd/web/router/router.go
// ...
func New(...) http.Handler {
    // ...
	// Sajikan file statis
	r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("./web/static"))))
    
    // ... rute-rute lainnya
}

4. Terapkan Class Utility

Terakhir, buka template HTML Anda (misal home.page.html) dan tambahkan class utility dari UnoCSS. Jangan lupa link ke stylesheet!

<!-- web/templates/home.page.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Toko Online Go</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body class="bg-gray-100 font-sans">
    <div class="container mx-auto p-4">
        <h1 class="text-3xl font-bold text-center my-8">Selamat Datang di Toko Kami!</h1>
        
        <div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6">
            {{range .Products}}
                <div class="product-card bg-white rounded-lg shadow-md overflow-hidden">
                    <a href="/products/{{.ID}}" class="block">
                        <img src="{{.ImageURL}}" alt="{{.Name}}" class="w-full h-48 object-cover">
                        <div class="p-4">
                            <h3 class="font-bold text-lg">{{.Name}}</h3>
                            <p class="text-gray-600 mt-2">Rp {{.Price}}</p>
                        </div>
                    </a>
                </div>
            {{else}}
                <p class="col-span-full text-center">Belum ada produk untuk ditampilkan.</p>
            {{end}}
        </div>
    </div>
</body>
</html>

Segarkan browser Anda dan lihatlah keajaibannya! Aplikasi Anda kini memiliki tampilan yang jauh lebih profesional.

Penutup & Langkah Selanjutnya

SELAMAT! Anda telah berhasil menyelesaikan seri tutorial ini dan membangun sebuah web app e-commerce dari nol menggunakan Go. Anda telah belajar tentang backend, database, autentikasi, upload file, dan bahkan styling frontend.

Ini adalah fondasi yang sangat kuat. Dari sini, Anda bisa melangkah lebih jauh dengan:

  • Menambahkan Keranjang Belanja: Menyimpan item di sesi atau database.
  • Proses Checkout: Mengintegrasikan dengan payment gateway.
  • Deployment: Mempelajari cara men-deploy aplikasi Go dan database Anda ke server.
  • Membuat API: Membangun API JSON untuk mendukung aplikasi mobile.

Terima kasih telah mengikuti perjalanan ini. Semoga Anda belajar banyak dan terinspirasi untuk terus membangun hal-hal luar biasa dengan Go. Selamat coding!

Untuk referensi, Anda dapat melihat kode final dari tutorial ini di repositori GitHub berikut: Source code lengkap

Diskusi

Tutorial Lainnya