Episode ini mengajarkan cara mengelola konfigurasi aplikasi: variabel lingkungan dengan os.Getenv, loading .env dengan godotenv, Viper untuk konfigurasi terstruktur, serta config struct yang diinjeksi ke handler. Kalian juga membangun graceful shutdown dengan signal.NotifyContext dan http.Server.

Nilai yang keras-kode seperti port, URL database, dan secret adalah musuh aplikasi production. Episode 10 mengajarkan konfigurasi yang benar: memisahkan nilai dari kode, memuatnya dari lingkungan, dan menyuntikkannya ke seluruh aplikasi.
Kalian juga akan membangun salah satu hal paling sering dilupakan: graceful shutdown. Server yang menerima sinyal terminasi harus berhenti dengan sopan — menyelesaikan request yang sedang berjalan — bukan diputus paksa.
Cara paling mendasar:
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
dbURL := os.Getenv("DATABASE_URL")os.Getenv("PORT") mengembalikan string kosong jika variabel belum di-set, sehingga kalian perlu nilai default. Pola ini sederhana dan portabel — seluruh platform modern mendukung environment variables.
export PORT=9090
export DATABASE_URL=postgres://postgres:secret@localhost:5432/chi_service
go run ./cmd/serverexport PORT=9090 menetapkan variabel untuk proses berjalan. Di production, environment variables disuntikkan oleh platform — Docker, Kubernetes, atau service seperti Vercel.
Untuk development lokal, muat file .env:
go get github.com/joho/godotenvfunc main() {
godotenv.Load()
port := os.Getenv("PORT")
...
}godotenv.Load() membaca file .env di direktori kerja lalu mengisi environment. Jangan pernah commit file .env — simpan sebagai .env.example saja.
Viper menambah lapisan: default, file config, dan auto-env sekaligus:
viper.SetDefault("PORT", "8080")
viper.SetDefault("DB_MAX_CONNS", 20)
viper.AutomaticEnv()
port := viper.GetString("PORT")
maxConns := viper.GetInt("DB_MAX_CONNS")viper.AutomaticEnv() otomatis memetakan environment variables ke key yang sama. viper.GetString("PORT") mengambil nilai dengan default yang sudah didefinisikan.
Rapikan seluruh nilai ke satu struct:
type Config struct {
Port string
DBURL string
MaxConns int
JWTSecret string
}
func loadConfig() Config {
return Config{
Port: viper.GetString("PORT"),
DBURL: viper.GetString("DATABASE_URL"),
MaxConns: viper.GetInt("DB_MAX_CONNS"),
JWTSecret: viper.GetString("JWT_SECRET"),
}
}loadConfig() mengumpulkan semua pembacaan environment di satu tempat — handler tidak pernah memanggil os.Getenv lagi.
cfg := loadConfig()
pool, _ := pgxpool.New(ctx, cfg.DBURL)
userRepo := repository.NewUserRepo(pool)
userHandler := handler.NewUserHandler(userRepo)
deps := Deps{Config: cfg, Users: userHandler}
http.ListenAndServe(":"+cfg.Port, Routes(deps))deps := Deps{Config: cfg, Users: userHandler} membangun dependency graph di main, lalu meneruskannya ke Routes dari episode 8. Konfigurasi mengalir ke bawah, tidak pernah diakses acak dari dalam handler.
signal.NotifyContext mengubah sinyal OS menjadi pembatalan context:
func main() {
ctx, stop := signal.NotifyContext(context.Background(),
os.Interrupt, syscall.SIGTERM)
defer stop()
srv := &http.Server{Addr: ":" + cfg.Port, Handler: Routes(deps)}
go func() {
if err := srv.ListenAndServe(); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(),
10*time.Second)
defer cancel()
srv.Shutdown(shutdownCtx)
}signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) membatalkan context saat Ctrl+C atau SIGTERM tiba. <-ctx.Done() menunggu sinyal, lalu srv.Shutdown(shutdownCtx) memberi waktu 10 detik bagi request yang sedang berjalan untuk selesai.
Tanpa graceful shutdown, restart deployment memotong request yang sedang diproses — pengguna mendapat error misterius. Dengan pola ini, proses menunggu request selesai, menutup koneksi database, lalu keluar dengan kode nol.
go run ./cmd/server &
kill -TERM $!kill -TERM $! mengirim SIGTERM ke proses server — kalian harus melihat log shutdown yang bersih, bukan panic.
Inti yang harus dibawa pulang:
os.Getenv..env lokal; Viper untuk konfigurasi terstruktur.Config struct dan injeksi ke handler.viper.AutomaticEnv memetakan environment ke key secara otomatis.signal.NotifyContext menangkap sinyal terminasi.srv.Shutdown memberi waktu request selesai sebelum proses keluar.Di episode 11 selanjutnya kita membahas kegagalan dan jejaknya: error handling dan logging — custom error type, handler yang mengembalikan error ke JSON, middleware recovery, serta logging terstruktur dengan slog.