Belajar Chi - Configuration & Environment
Series/Belajar Chi/Episode 10
Episode 10 of 23

Belajar Chi - Configuration & Environment

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.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Pendahuluan

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.

Variabel Lingkungan

os.Getenv

Cara paling mendasar:

Membaca variabel lingkungan
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.

Mengatur dari Shell

Set variabel lingkungan
export PORT=9090
export DATABASE_URL=postgres://postgres:secret@localhost:5432/chi_service
go run ./cmd/server

export PORT=9090 menetapkan variabel untuk proses berjalan. Di production, environment variables disuntikkan oleh platform — Docker, Kubernetes, atau service seperti Vercel.

godotenv dan Viper

godotenv untuk File .env

Untuk development lokal, muat file .env:

Install godotenv
go get github.com/joho/godotenv
Muat file .env
func 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 untuk Konfigurasi Terstruktur

Viper menambah lapisan: default, file config, dan auto-env sekaligus:

Konfigurasi dengan Viper
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.

Config Struct dan Injection

Satu Struktur Konfigurasi

Rapikan seluruh nilai ke satu struct:

Config 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.

Injeksi ke Seluruh Lapisan

Injeksi config ke handler
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.

Graceful Shutdown

Menangkap Sinyal

signal.NotifyContext mengubah sinyal OS menjadi pembatalan context:

Graceful shutdown
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.

Kenapa Ini Penting

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.

Test sinyal terminasi
go run ./cmd/server &
kill -TERM $!

kill -TERM $! mengirim SIGTERM ke proses server — kalian harus melihat log shutdown yang bersih, bukan panic.

Penutup

Inti yang harus dibawa pulang:

  • Jangan keras-kode nilai; baca dari environment dengan os.Getenv.
  • godotenv untuk .env lokal; Viper untuk konfigurasi terstruktur.
  • Kumpulkan semua nilai ke 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.

Belajar Chi - Configuration & Environment | Belajar Chi