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:
- Menampilkan Semua Produk di halaman utama.
- Membuat Halaman Detail Produk yang dinamis.
- 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