Episode ini mengelola konfigurasi aplikasi Gin: variabel lingkungan dengan os.Getenv, file .env via godotenv, dan Viper untuk konfigurasi terpusat. Kalian juga belajar menyusun config struct serta implementasi graceful shutdown dengan http.Server dan signal.NotifyContext.

Sebuah aplikasi tidak boleh memuat rahasia atau pengaturan di dalam kode. Episode 10 ini membedah configuration & environment: cara membaca variabel lingkungan, memuat file .env untuk pengembangan, mengelola konfigurasi kompleks dengan Viper, dan yang tidak kalah penting — menghentikan server dengan bersih lewat graceful shutdown.
Kenapa bagian ini krusial? Koneksi database, port, dan token seharusnya bisa berbeda antara lingkungan development, staging, dan production tanpa mengubah kode. Graceful shutdown memastikan request yang sedang berjalan selesai sebelum proses mati, sehingga pengguna tidak mengalami error di tengah pemrosesan.
Cara paling dasar membaca konfigurasi adalah os.Getenv. Untuk variabel yang wajib ada, kombinasikan dengan os.LookupEnv:
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
dbURL, ok := os.LookupEnv("DATABASE_URL")
if !ok {
log.Fatal("DATABASE_URL wajib diisi")
}os.Getenv("PORT") mengembalikan string kosong jika variabel tidak ada, sehingga kalian perlu fallback. os.LookupEnv mengembalikan dua nilai sehingga tahu pasti apakah variabel terdefinisi — penting untuk variabel wajib seperti DATABASE_URL.
Di development, menyetel puluhan variabel lewat terminal tidak praktis. godotenv memuat file .env ke environment:
go get github.com/joho/godotenvPORT=8080
DATABASE_URL=postgres://arman:rahasia@localhost:5432/belajargin?sslmode=disable
REDIS_ADDR=localhost:6379
LOG_LEVEL=infofunc main() {
if err := godotenv.Load(); err != nil {
log.Println("tidak ada .env, pakai environment system")
}
cfg := config.Load()
// ...
}Panggil godotenv.Load() di awal main sebelum membaca konfigurasi. Jangan pernah meng-commit file .env — gunakan .env.example sebagai templat. Di production, variabel disuntikkan langsung oleh orchestrator atau platform, bukan dari file.
Untuk konfigurasi yang lebih kompleks, viper menggabungkan file konfigurasi, environment, dan flag:
go get github.com/spf13/viperviper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
log.Printf("config file tidak terbaca: %v", err)
}viper.AutomaticEnv() membuat setiap key bisa dioverride variabel lingkungan dengan pola nama yang sama. Contohnya key database.url bisa dioverride variabel DATABASE.URL atau pola lain sesuai konfigurasi. Ini memberi prioritas: environment mengalahkan file.
Kumpulkan semua pengaturan dalam satu struct agar bisa diinjeksi:
type Config struct {
Port string
DatabaseURL string
MaxConns int
RedisAddr string
LogLevel string
}
func Load() Config {
return Config{
Port: os.Getenv("PORT"),
DatabaseURL: os.Getenv("DATABASE_URL"),
MaxConns: mustInt("MAX_CONNS", 10),
RedisAddr: os.Getenv("REDIS_ADDR"),
LogLevel: os.Getenv("LOG_LEVEL"),
}
}Fungsi config.Load() Config menjadi satu-satunya tempat membaca environment. Handler dan service tidak perlu memanggil os.Getenv lagi — mereka menerima Config lewat konstruktor seperti pola injeksi di episode 8.
Dengan injeksi, handler bisa diuji dengan config apa pun:
NewServer(cfg config.Config) menyuntikkan config ke seluruh lapisan. Karena semua ketergantungan datang dari satu tempat, mengganti environment cukup mengubah nilai environment, bukan kode.
engine.Run langsung memblokir goroutine, sehingga sulit menghentikan dengan bersih. Ganti dengan http.Server dan tangani sinyal:
srv := &http.Server{
Addr: ":" + cfg.Port,
Handler: r,
ReadTimeout: 10 * time.Second,
WriteTimeout: 15 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("server gagal: %v", err)
}
}()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Fatalf("shutdown gagal: %v", err)
}
log.Println("server berhenti dengan bersih")signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) membatalkan context ketika pengguna menekan Ctrl+C atau orchestrator mengirim SIGTERM. Setelah itu srv.Shutdown(shutdownCtx) menunggu request aktif selesai maksimal 10 detik, lalu menutup koneksi. Inilah pola yang akan dipakai ulang di episode 21 untuk arsitektur production.
Inti yang harus dibawa pulang:
os.Getenv dengan fallback, os.LookupEnv untuk variabel wajib.godotenv memuat file .env untuk development, jangan di-commit.viper menggabungkan file konfigurasi dan environment dengan prioritas environment.config.Config lalu injeksikan ke handler.http.Server + signal.NotifyContext untuk graceful shutdown.srv.Shutdown memberi waktu request aktif selesai sebelum proses mati.Di episode 11 selanjutnya kita akan membedah error handling & logging — menangkap error dengan c.Error, custom error type, middleware error handler dengan response JSON terpadu, serta integrasi slog ke middleware Gin untuk log yang terstruktur.