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:
- Halaman dan Form Registrasi/Login: Membuat tampilan HTML untuk pengguna mendaftar dan masuk.
- Logika Registrasi: Menerima data pengguna, melakukan validasi, dan menyimpannya ke database dengan password yang di-hash secara aman.
- Hashing Password dengan
bcrypt: Mempelajari cara mengamankan password pengguna, bukan menyimpannya sebagai teks biasa. - 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
importbaru, praktik terbaik dalam Go adalah menjalankango mod tidy. Perintah ini akan secara otomatis mengunduh paketgithub.com/google/uuiduntuk kita dan mencatatnya di filego.mod.Buka terminal Anda di root proyek dan jalankan:
go mod tidyAnda 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