Membangun Web App E-Commerce dengan Go: #4 - Autentikasi Pengguna (Registrasi & Login)

Membangun Web App E-Commerce dengan Go: #4 - Autentikasi Pengguna (Registrasi & Login)

Amankan aplikasi Anda! Di bagian ini kita akan membangun sistem registrasi dan login pengguna dari nol, termasuk hashing password yang aman menggunakan bcrypt.

Penulis: Novian Hidayat
Tanggal: 1 Juli 2025

Selamat datang kembali! Sejauh ini, aplikasi e-commerce kita sudah memiliki tampilan, tetapi belum bisa mengenali siapa penggunanya. Di bagian keempat yang krusial ini, kita akan mengimplementasikan sistem autentikasi.

Kita akan membangun fitur berikut:

  1. Halaman dan Form Registrasi/Login: Membuat tampilan HTML untuk pengguna mendaftar dan masuk.
  2. Logika Registrasi: Menerima data pengguna, melakukan validasi, dan menyimpannya ke database dengan password yang di-hash secara aman.
  3. Hashing Password dengan bcrypt: Mempelajari cara mengamankan password pengguna, bukan menyimpannya sebagai teks biasa.
  4. Logika Login: Memverifikasi email dan password pengguna.

Ini adalah langkah besar untuk membuat aplikasi kita menjadi aplikasi yang sesungguhnya. Mari kita mulai!

Langkah 1: Tambahkan bcrypt dan Buat Model User

Pertama, kita butuh library untuk hashing password. bcrypt adalah standar industri yang sudah teruji.

Instal paket bcrypt dari golang.org melalui terminal:

go get golang.org/x/crypto/bcrypt

Selanjutnya, mari kita definisikan struktur User di Go yang sesuai dengan tabel users di database kita. Buat direktori dan file baru:

mkdir -p internal/models
touch internal/models/user.go

Isi file internal/models/user.go dengan kode berikut:

// internal/models/user.go
package models

import (
	"time"
	"github.com/google/uuid"
)

type User struct {
	ID           uuid.UUID `db:"id"`
	Email        string    `db:"email"`
	PasswordHash string    `db:"password_hash"`
	FullName     string    `db:"full_name"`
	CreatedAt    time.Time `db:"created_at"`
	UpdatedAt    time.Time `db:"updated_at"`
}

Catatan: Kita menggunakan github.com/google/uuid agar lebih mudah bekerja dengan tipe data UUID dari database.

“Setelah menambahkan import baru, praktik terbaik dalam Go adalah menjalankan go mod tidy. Perintah ini akan secara otomatis mengunduh paket github.com/google/uuid untuk kita dan mencatatnya di file go.mod.

Buka terminal Anda di root proyek dan jalankan:

go mod tidy

Anda akan melihat terminal mengunduh paket tersebut. Sekarang, semua dependensi kita sudah sinkron!”

Langkah 2: Membuat Halaman & Form Registrasi

Kita butuh halaman dengan form agar pengguna bisa mendaftar.

1. Buat Template HTML

Buat file baru web/templates/register.page.html:

<!-- web/templates/register.page.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Registrasi Pengguna Baru</title>
    <style>
        body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; min-height: 80vh; background-color: #f4f4f9; }
        .container { width: 100%; max-width: 400px; }
        form { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
        h2 { text-align: center; color: #2c3e50; margin-top: 0; }
        .form-group { margin-bottom: 1rem; }
        label { display: block; margin-bottom: 0.5rem; }
        input { width: 95%; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; }
        button { width: 100%; padding: 0.75rem; border: none; background-color: #3498db; color: white; border-radius: 4px; font-size: 1rem; cursor: pointer; }
        button:hover { background-color: #2980b9; }
    </style>
</head>
<body>
    <div class="container">
        <form action="/register" method="POST">
            <h2>Buat Akun Baru</h2>
            <div class="form-group">
                <label for="full_name">Nama Lengkap</label>
                <input type="text" id="full_name" name="full_name" required>
            </div>
            <div class="form-group">
                <label for="email">Email</label>
                <input type="email" id="email" name="email" required>
            </div>
            <div class="form-group">
                <label for="password">Password</label>
                <input type="password" id="password" name="password" required>
            </div>
            <button type="submit">Daftar</button>
        </form>
    </div>
</body>
</html>

2. Buat Handler untuk Menampilkan dan Memproses Form

Kita butuh dua handler: satu untuk menampilkan form (GET /register) dan satu lagi untuk memproses data yang dikirim (POST /register).

Buat file baru cmd/web/handler/auth.go:

// cmd/web/handler/auth.go
package handler

import (
	"log"
	"net/http"

	"github.com/kodekilat/go-ecommerce/cmd/web/view" // Ganti dengan path modul Anda
	"golang.org/x/crypto/bcrypt"
)

// ShowRegistrationForm menampilkan halaman registrasi
func ShowRegistrationForm(w http.ResponseWriter, r *http.Request) {
	view.Render(w, "register.page.html", nil)
}

// HandleRegistration memproses data dari form registrasi
func HandleRegistration(w http.ResponseWriter, r *http.Request) {
	// 1. Parse form data
	err := r.ParseForm()
	if err != nil {
		http.Error(w, "Gagal memproses form", http.StatusBadRequest)
		return
	}

	fullName := r.PostForm.Get("full_name")
	email := r.PostForm.Get("email")
	password := r.PostForm.Get("password")

	// 2. Validasi sederhana
	if fullName == "" || email == "" || password == "" {
		http.Error(w, "Semua field harus diisi", http.StatusBadRequest)
		return
	}

	// 3. Hash password dengan bcrypt
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		log.Printf("Gagal hash password: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}
	
	// 4. Simpan ke database (akan kita implementasikan di langkah berikutnya)
	log.Printf("User baru terdaftar: Nama=%s, Email=%s, Hash=%s", fullName, email, string(hashedPassword))

	// 5. Redirect ke halaman login atau halaman sukses
	http.Redirect(w, r, "/login", http.StatusSeeOther)
}

3. Daftarkan Rute Baru

Buka cmd/web/router/router.go dan tambahkan rute untuk registrasi:

// cmd/web/router/router.go
// ... (imports)

func New() http.Handler {
	r := chi.NewRouter()

	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)

	r.Get("/", handler.ShowHomePage)

	// Rute baru untuk autentikasi
	r.Get("/register", handler.ShowRegistrationForm)
	r.Post("/register", handler.HandleRegistration)

	return r
}

Sekarang, jalankan aplikasi Anda dan buka http://localhost:8080/register. Anda akan melihat form registrasi. Coba isi dan kirim, lalu periksa log di terminal Anda!

Langkah 3: Menyimpan Pengguna ke Database

Logika di atas belum menyimpan data. Mari kita buat fungsi untuk berinteraksi dengan tabel users.

Buat file baru internal/repository/user_repo.go:

mkdir -p internal/repository
touch internal/repository/user_repo.go

Isi file tersebut dengan kode berikut:

// internal/repository/user_repo.go
package repository

import (
	"context"
	
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/kodekilat/go-ecommerce/internal/models" // Ganti dengan path modul Anda
)

type UserRepository struct {
	DB *pgxpool.Pool
}

func (r *UserRepository) CreateUser(user *models.User) error {
	query := `
		INSERT INTO users (full_name, email, password_hash)
		VALUES ($1, $2, $3)
		RETURNING id, created_at, updated_at
	`
	err := r.DB.QueryRow(context.Background(), query, user.FullName, user.Email, user.PasswordHash).Scan(&user.ID, &user.CreatedAt, &user.UpdatedAt)
	return err
}

Langkah 4: Hubungkan Handler dengan Repository

Sekarang, kita perlu membuat dependency injection: memberikan akses database ke handler kita.

1. Modifikasi main.go untuk membuat instance repository

// cmd/web/main.go
// ... (imports)
import "github.com/kodekilat/go-ecommerce/internal/repository" // Tambahkan import ini

func main() {
	db, err := database.NewConnection()
	if err != nil {
		log.Fatalf("Tidak dapat terhubung ke database: %v", err)
	}
	defer db.Close()

    // Buat instance user repository
	userRepo := &repository.UserRepository{DB: db}

    // Teruskan repository ke router
	appRouter := router.New(userRepo)

	// ... (sisa kode)
}

2. Ubah router.go untuk menerima repository

// cmd/web/router/router.go
// ... (imports)
import "github.com/kodekilat/go-ecommerce/internal/repository" // Tambahkan import ini

// Fungsi New sekarang menerima dependensi
func New(userRepo *repository.UserRepository) http.Handler {
	r := chi.NewRouter()
	// ... (middleware)
	
    // Buat instance handler dengan dependensi
	authHandler := &handler.AuthHandler{UserRepo: userRepo}

	r.Get("/", handler.ShowHomePage)
	r.Get("/register", authHandler.ShowRegistrationForm)
	r.Post("/register", authHandler.HandleRegistration)

	return r
}

3. Ubah auth.go untuk menggunakan repository

// cmd/web/handler/auth.go
package handler

import (
	// ... (imports)
	"github.com/kodekilat/go-ecommerce/internal/models"
	"github.com/kodekilat/go-ecommerce/internal/repository"
)

// Definisikan struct untuk handler agar bisa menampung dependensi
type AuthHandler struct {
	UserRepo *repository.UserRepository
}

// Ubah fungsi menjadi method dari AuthHandler
func (h *AuthHandler) ShowRegistrationForm(w http.ResponseWriter, r *http.Request) {
	view.Render(w, "register.page.html", nil)
}

func (h *AuthHandler) HandleRegistration(w http.ResponseWriter, r *http.Request) {
	// ... (kode parse form dan hash password sama seperti sebelumnya) ...

	// Buat model user baru
	newUser := &models.User{
		FullName:     fullName,
		Email:        email,
		PasswordHash: string(hashedPassword),
	}

	// Panggil method dari repository untuk menyimpan user
	err = h.UserRepo.CreateUser(newUser)
	if err != nil {
		// (Tambahkan pengecekan error duplikat email di sini nanti)
		log.Printf("Gagal menyimpan user: %v", err)
		http.Error(w, "Gagal mendaftarkan pengguna", http.StatusInternalServerError)
		return
	}

	log.Printf("User baru berhasil disimpan dengan ID: %s", newUser.ID)
	http.Redirect(w, r, "/login", http.StatusSeeOther)
}

Setelah semua perubahan ini, jalankan aplikasi Anda lagi. Coba daftarkan pengguna baru. Kali ini, data pengguna tersebut akan benar-benar tersimpan di database PostgreSQL Anda! Anda bisa memeriksanya menggunakan DBeaver, pgAdmin, atau psql.

Penutup

Kerja yang sangat bagus! Kita telah berhasil mengimplementasikan salah satu bagian paling fundamental dan kompleks dari aplikasi web: registrasi pengguna dengan keamanan password yang layak.

Di bagian selanjutnya, kita akan melengkapi siklus ini dengan membangun:

  • Halaman dan logika Login.
  • Manajemen Sesi agar pengguna tetap login saat berpindah halaman.

Teruslah bersemangat, bagian terbaiknya akan segera datang!

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

Diskusi

Tutorial Lainnya