Episode ini menghubungkan Echo dengan database: koneksi PostgreSQL dengan pgx, database/sql, GORM, dan sqlc, connection pooling, migrasi skema, serta implementasi CRUD lengkap dalam handler Echo mengikuti pola repository dari episode 8.

Hampir semua REST API akhirnya berujung pada penyimpanan data. Episode ini menghubungkan arsitektur dari episode 8 dengan database nyata: PostgreSQL sebagai pilihan utama, disertai pendekatan dengan database/sql, GORM, dan sqlc. Kalian akan melihat pola repository benar-benar bekerja.
Episode 9 ini membahas koneksi PostgreSQL dengan pgx, pooling, migrasi skema, lalu implementasi CRUD lengkap dalam handler Echo.
Mulai dengan instance PostgreSQL lokal. Cara paling bersih adalah Docker:
docker run --name belajar-echo-pg \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=belajar_echo \
-p 5432:5432 \
-d postgres:16Setelah kontainer berjalan, kalian punya database belajar_echo di localhost:5432. Catat kredensialnya — akan dipakai sebagai DATABASE_URL di episode 10.
pgx adalah driver PostgreSQL terpopuler untuk Go, dan pgxpool menyediakan connection pool yang sehat untuk server HTTP:
import (
"context"
"github.com/jackc/pgx/v5/pgxpool"
)
func NewDB(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
pool, err := pgxpool.New(ctx, dsn)
if err != nil {
return nil, err
}
if err := pool.Ping(ctx); err != nil {
pool.Close()
return nil, err
}
return pool, nil
}pgxpool.New membuat pool yang siap dipakai bersamaan oleh banyak goroutine — persis yang dibutuhkan server Echo yang menangani banyak request sekaligus.
Gunakan query berparameter untuk menghindari SQL injection:
func (r *userRepo) FindByID(ctx context.Context, id int) (*model.User, error) {
row := r.pool.QueryRow(ctx,
"SELECT id, name, email FROM users WHERE id = $1", id)
var u model.User
if err := row.Scan(&u.ID, &u.Name, &u.Email); err != nil {
return nil, err
}
return &u, nil
}Placeholder $1 diisi dengan nilai variabel, bukan digabung string. Ini prinsip non-negotiable untuk semua query database.
GORM adalah ORM paling populer di ekosistem Go. Cocok bila kalian ingin menghindari menulis SQL berulang:
go get gorm.io/gorm
go get gorm.io/driver/postgresimport (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
db.AutoMigrate(&model.User{})db.AutoMigrate membuat atau memperbarui tabel dari definisi struct. Nyaman untuk development, tapi di production migrasi sebaiknya dikelola tool khusus.
sqlc menempuh jalur sebaliknya: kalian menulis SQL, dan kode Go tipe-aman dibangkitkan dari file .sql:
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
sqlc initSetiap pendekatan punya trade-off: database/sql minimal dan transparan, GORM produktif, sqlc tipe-aman. Pilih sesuai kebutuhan tim; semuanya berjalan baik di balik antarmuka repository.
Untuk production, gunakan tool migrasi versi. goose adalah pilihan sederhana yang berbasis SQL:
go install github.com/pressly/goose/v3/cmd/goose@latest
goose -dir migrations postgres \
"host=localhost user=postgres password=postgres dbname=belajar_echo" upFile migrasi disimpan di direktori migrations/ dengan urutan versi. Perintah goose up menerapkan semua migrasi yang belum berjalan.
Dengan pola dari episode 8, CRUD dibangun tiga lapisan. Repository menyimpan query, service mengatur alur, dan handler menghubungkan ke HTTP:
func (h *UserHandler) Create(c echo.Context) error {
var input model.CreateUserInput
if err := c.Bind(&input); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
user, err := h.svc.Create(c.Request().Context(), input)
if err != nil {
return err
}
return c.JSON(http.StatusCreated, user)
}Handler tetap tipis: bind, panggil service, kembalikan response. Semua logika transaksi dan validasi bisnis hidup di service, dan semua query di repository.
Service juga yang mengelola transaksi — misalnya membuat user sekaligus mencatat audit log:
tx, err := s.db.BeginTx(ctx, nil)
defer tx.Rollback()
if _, err := tx.Exec(ctx, "INSERT INTO users ..."); err != nil {
return err
}
if _, err := tx.Exec(ctx, "INSERT INTO audit_logs ..."); err != nil {
return err
}
return tx.Commit()Pola BeginTx dan Commit memastikan beberapa operasi menjadi satu kesatuan yang atomik.
Episode 9 menghubungkan Echo dengan dunia data: pool koneksi pgx yang aman untuk concurrency, query berparameter untuk keamanan, pilihan GORM dan sqlc sesuai kebutuhan, migrasi terkelola dengan goose, serta CRUD lengkap yang tersusun dalam pola repository, service, dan handler.
Inti yang harus dibawa pulang:
pgxpool menyediakan koneksi aman untuk concurrency tinggi.Di episode 10 selanjutnya kita akan membahas configuration & environment — membaca variabel lingkungan dengan os.Getenv, godotenv, dan Viper, struct config terpusat, server HTTP custom dengan ReadTimeout dan WriteTimeout, serta graceful shutdown yang bersih saat server menerima sinyal berhenti.