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
import
baru, praktik terbaik dalam Go adalah menjalankango mod tidy
. Perintah ini akan secara otomatis mengunduh paketgithub.com/google/uuid
untuk kita dan mencatatnya di filego.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