For the complete documentation index, see llms.txt. This page is also available as Markdown.
Database
API tanpa database hanyalah kumpulan data statis. Di bab ini kita akan menghubungkan API dengan PostgreSQL sebagai penyimpanan data permanen. Kita akan menggunakan paket database/sql bawaan Go — sederhana, performa tinggi, dan tanpa ORM yang kompleks.
Pembahasan dibagi menjadi tiga bagian:
Migration – Membuat struktur tabel
Seed – Mengisi data awal
ListUsers – Mengambil data dari database (bukan hardcode)
📂 Kode Lengkap Bab Ini:
Seluruh kode yang dibahas di bab ini tersedia di GitHub:
Catatan: Koneksi string di atas menggunakan format URL. Pastikan menginstal driver PostgreSQL terlebih dahulu:
gogetgithub.com/lib/pq
4.2 Migration
Migration adalah cara version-controlled untuk membuat dan mengubah skema database. Kita akan menggunakan library go-libs/migration karena cukup sederhana.
Langkah 1: Membuat Folder dan File Migration
Buat folder migration/ di root proyek, lalu buat file 1_0001_users.sql dengan struktur penamaan:
Prefix
Kegunaan
1_
Create Table
2_
Alter Table
3_
Seed Data
Langkah 2: Isi File Migration
Langkah 3: Integrasi Migration ke main.go
Kita perlu menambahkan kemampuan menjalankan migration dari command line:
Jalankan migration:
4.3 Seed Data
Seed adalah proses mengisi data awal ke database. Buat file 3_0001_users.sql di folder migration/:
Peringatan: Password masih dalam bentuk plain text! Di bab selanjutnya kita akan membahas hashing dengan bcrypt.
Jalankan migration lagi untuk mengisi seed:
Perintah yang sama akan mengeksekusi semua file migration yang belum dijalankan (termasuk seed).
4.4 Mengambil Data dari Database
Sekarang kita ubah handler ListUsers untuk mengambil data langsung dari database, bukan hardcode.
Membuat Struct dengan Dependency Injection
Pertama, buat struct Users yang menerima koneksi database melalui dependency injection (DI). DI membuat kode lebih mudah di-test karena kita bisa mengganti dependency kapan saja.
Method List yang Terintegrasi Database
Menghubungkan ke Server
Update fungsi main untuk menggunakan userService.List sebagai handler:
4.5 Kode Lengkap main.go
4.6 Menjalankan dan Menguji
Response akan berisi data dari database PostgreSQL:
Ringkasan Bab 4
Di bab ini kita telah belajar:
Migration – Mengelola skema database dengan file SQL version-controlled
Seed – Mengisi data awal untuk development/testing
Dependency Injection – Menyuntikkan koneksi database ke handler
Database Query – Menggunakan database/sql untuk mengambil data
CLI Pattern – Menambahkan perintah migrate tanpa mengganggu server HTTP
Pola penting yang diperkenalkan:
File migration dengan prefix numerik (urutan eksekusi jelas)
Struct Users sebagai receiver method (bukan fungsi global)
Database connection dibuat sekali di main dan di-inject
Yang akan datang:
❌ Masih ada password plain text
❌ Belum ada validasi input
❌ Semua kode dalam satu file (akan dipisah sesuai Clean Architecture)
Pada bab berikutnya, kita akan membahas Clean Architecture — memisahkan kode ke layer-layer yang bertanggung jawab agar framework kita mudah di-maintain dan di-test.
-- Enable extension yang diperlukan
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- Untuk pencarian nana
-- Membuat tabel users
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL,
username VARCHAR(100) NOT NULL,
password VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc', now()),
updated_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc', now()),
deleted_at TIMESTAMPTZ NULL
);
-- =====================================================
-- INDEX SET (Minimum Viable Index untuk awal project)
-- =====================================================
-- 1. Unique partial index untuk username (wajib, untuk login/auth)
CREATE UNIQUE INDEX idx_users_username_unique
ON users(username)
WHERE deleted_at IS NULL;
-- 2. Unique partial index untuk email (wajib, untuk komunikasi)
CREATE UNIQUE INDEX idx_users_email_unique
ON users(email)
WHERE deleted_at IS NULL;
-- 3. Index untuk pagination/sorting
CREATE INDEX idx_users_created_at_active
ON users(created_at DESC)
WHERE deleted_at IS NULL;
-- 4. Partial index untuk filter is_active
CREATE INDEX idx_users_is_active
ON users(is_active)
WHERE deleted_at IS NULL AND is_active = true;
-- 5. Trigram index untuk pencarian name (buat hanya jika fitur search diperlukan)
CREATE INDEX idx_users_name_trgm
ON users USING gin(name gin_trgm_ops)
WHERE deleted_at IS NULL;
-- =====================================================
-- TRIGGER untuk auto-update updated_at
-- =====================================================
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = timezone('utc', now());
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
func main() {
db, err := openDB()
if err != nil {
log.Fatalf("error: opening database: %s", err)
}
defer db.Close()
flag.Parse()
// Jika argumen = "migrate", jalankan migration lalu exit
if len(flag.Args()) > 0 && flag.Arg(0) == "migrate" {
if err := migration.Migrate(db, "migration"); err != nil {
log.Fatalf("error: running migrations: %s", err)
}
log.Printf("migrations completed successfully")
return
}
// ... lanjut ke server HTTP ...
}