Pola konfigurasi lingkungan yang menjaga URL, kunci, dan feature flag tetap keluar dari kode di web, backend, dan mobile untuk dev, staging, dan prod.

Konfigurasi yang di-hardcode terasa baik pada hari pertama. Lalu Anda butuh environment staging, API kedua, atau switch fitur cepat, dan perubahan “sederhana” itu berubah menjadi risiko rilis. Solusinya jelas: jangan simpan nilai environment di berkas sumber — letakkan di pengaturan yang bisa diprediksi.
Pelakunya biasanya gampang dikenali:
“Ubah saja untuk prod” menciptakan kebiasaan suntingan menit terakhir. Suntingan itu sering melewatkan review, tes, dan keterulangan. Satu orang mengubah URL, yang lain mengubah kunci, dan sekarang Anda tak bisa menjawab pertanyaan dasar: konfigurasi apa persisnya yang dikirim dengan build ini?
Skenario umum: Anda membangun versi mobile baru terhadap staging, lalu seseorang mengganti URL ke prod tepat sebelum rilis. Backend berubah lagi keesokan hari, dan Anda harus rollback. Jika URL di-hardcode, rollback berarti update aplikasi lagi. Pengguna menunggu, tiket support menumpuk.
Tujuannya adalah skema sederhana yang bekerja di web app, backend Go, dan aplikasi Flutter:
Dev, staging, dan prod seharusnya terasa seperti aplikasi yang sama berjalan di tiga tempat berbeda. Intinya adalah mengubah nilai, bukan perilaku.
Yang seharusnya berubah adalah apa pun yang terkait dengan tempat aplikasi berjalan atau siapa yang menggunakannya: base URL dan hostname, kredensial, integrasi sandbox vs nyata, dan kontrol keselamatan seperti level logging atau pengaturan keamanan lebih ketat di prod.
Yang harus tetap sama adalah logika dan kontrak antar bagian. Route API, bentuk request/response, nama fitur, dan aturan bisnis inti tidak boleh berbeda berdasarkan environment. Jika staging berperilaku berbeda, ia berhenti menjadi latihan yang andal untuk production.
Aturan praktis untuk “environment baru” vs “nilai konfigurasi baru”: buat environment baru hanya ketika Anda butuh sistem terisolasi (data, akses, dan risiko terpisah). Jika Anda hanya butuh endpoint berbeda atau angka berbeda, tambahkan nilai konfigurasi saja.
Contoh: Anda ingin menguji penyedia pencarian baru. Jika aman untuk mengaktifkannya untuk kelompok kecil, pertahankan satu staging dan tambahkan feature flag. Jika membutuhkan database terpisah dan kontrol akses ketat, itu saatnya environment baru.
Setup yang baik melakukan satu hal dengan baik: membuatnya sulit mengirim URL dev, kunci tes, atau fitur yang belum selesai secara tidak sengaja.
Gunakan tiga lapis yang sama untuk setiap aplikasi (web, backend, mobile):
Untuk menghindari kebingungan, pilih satu sumber kebenaran per aplikasi dan patuhi itu. Misalnya, backend membaca dari environment variable saat startup, web app membaca dari variabel build-time atau file runtime kecil, dan mobile membaca dari berkas environment kecil yang dipilih saat build. Konsistensi di dalam tiap aplikasi lebih penting daripada memaksakan mekanisme yang persis sama di semua tempat.
Skema sederhana yang dapat dipakai ulang terlihat seperti ini:
Beri setiap item konfigurasi nama yang jelas yang menjawab tiga pertanyaan: apa itu, dimana berlaku, dan tipe apa.
Konvensi praktis:
Dengan cara ini, tak ada yang perlu menebak apakah “BASE_URL” untuk aplikasi React, service Go, atau aplikasi Flutter.
Kode React berjalan di browser pengguna, jadi apa pun yang Anda kirim bisa dibaca. Tujuannya sederhana: simpan rahasia di server, dan biarkan browser membaca hanya pengaturan “aman” seperti API base URL, nama aplikasi, atau feature toggle non-sensitif.
Konfigurasi build-time disuntik saat Anda membangun bundle. Cocok untuk nilai yang jarang berubah dan aman diekspos.
Konfigurasi runtime dimuat saat aplikasi mulai (misalnya dari file JSON kecil yang disajikan bersama app, atau global yang disuntik). Lebih baik untuk nilai yang mungkin ingin Anda ubah setelah deploy, seperti mengganti API base URL antar environment.
Aturan sederhana: jika mengubahnya seharusnya tidak memerlukan rebuild UI, jadikan runtime.
Simpan berkas lokal untuk developer (tidak dikomit) dan atur nilai nyata di pipeline deploy Anda.
.env.local (gitignored) dengan misalnya VITE_API_BASE_URL=http://localhost:8080VITE_API_BASE_URL sebagai environment variable di job build, atau masukkan ke file runtime config yang dibuat saat deployContoh runtime (disajikan di samping app Anda):
{ "apiBaseUrl": "https://api.staging.example.com", "features": { "newCheckout": false } }
Lalu muat sekali saat startup dan simpan di satu tempat:
export async function loadConfig() {
const res = await fetch('/config.json', { cache: 'no-store' });
return res.json();
}
Perlakukan segala sesuatu di env var React sebagai publik. Jangan letakkan password, private API key, atau URL database di web app.
Contoh aman: API base URL, Sentry DSN (publik), versi build, dan flag fitur sederhana.
Konfigurasi backend lebih aman ketika bertipe, dimuat dari environment variable, dan divalidasi sebelum server mulai menerima trafik.
Mulai dengan memutuskan apa yang backend butuhkan untuk berjalan, dan buat nilai-nilai itu eksplisit. Nilai “harus ada” tipikal:
APP_ENV (dev, staging, prod)HTTP_ADDR (misalnya :8080)DATABASE_URL (Postgres DSN)PUBLIC_BASE_URL (digunakan untuk callback dan link)API_KEY (untuk layanan pihak ketiga)Kemudian muat ke dalam struct dan fail fast jika ada yang hilang atau salah format. Dengan begitu Anda menemukan masalah dalam hitungan detik, bukan setelah deploy parsial.
package config
import (
"errors"
"net/url"
"os"
"strings"
)
type Config struct {
Env string
HTTPAddr string
DatabaseURL string
PublicBaseURL string
APIKey string
}
func Load() (Config, error) {
c := Config{
Env: mustGet("APP_ENV"),
HTTPAddr: getDefault("HTTP_ADDR", ":8080"),
DatabaseURL: mustGet("DATABASE_URL"),
PublicBaseURL: mustGet("PUBLIC_BASE_URL"),
APIKey: mustGet("API_KEY"),
}
return c, c.Validate()
}
func (c Config) Validate() error {
if c.Env != "dev" && c.Env != "staging" && c.Env != "prod" {
return errors.New("APP_ENV must be dev, staging, or prod")
}
if _, err := url.ParseRequestURI(c.PublicBaseURL); err != nil {
return errors.New("PUBLIC_BASE_URL must be a valid URL")
}
if !strings.HasPrefix(c.DatabaseURL, "postgres://") {
return errors.New("DATABASE_URL must start with postgres://")
}
return nil
}
func mustGet(k string) string {
v, ok := os.LookupEnv(k)
if !ok || strings.TrimSpace(v) == "" {
panic("missing env var: " + k)
}
return v
}
func getDefault(k, def string) string {
if v, ok := os.LookupEnv(k); ok && strings.TrimSpace(v) != "" {
return v
}
return def
}
Ini menjaga DSN database, kunci API, dan URL callback keluar dari kode dan git. Di setup terhost, Anda menyuntik env var ini per environment sehingga dev, staging, dan prod bisa berbeda tanpa mengubah satu baris pun.
Aplikasi Flutter biasanya butuh dua lapis konfigurasi: flavor build-time (apa yang Anda kirim) dan pengaturan runtime (apa yang bisa diubah aplikasi tanpa rilis baru). Memisahkan keduanya menghentikan “cukup ubah satu URL” menjadi rebuild darurat.
Buat tiga flavor: dev, staging, prod. Flavor mengendalikan hal yang harus tetap saat build, seperti nama aplikasi, bundle id, penandatanganan, proyek analytics, dan apakah tools debug diaktifkan.
Lalu kirim hanya default non-sensitif dengan --dart-define (atau CI) sehingga Anda tak pernah menaruhnya langsung di kode:
ENV=stagingDEFAULT_API_BASE=https://api-staging.example.comCONFIG_URL=https://config.example.com/mobile.jsonDi Dart, baca dengan String.fromEnvironment dan bangun objek AppConfig sederhana sekali saat startup.
Jika Anda ingin menghindari rebuild untuk perubahan endpoint kecil, jangan perlakukan API base URL sebagai konstanta. Fetch berkas config kecil saat peluncuran app (dan cache). Flavor hanya menentukan dari mana mengambil config.
Pembagian praktis:
Jika Anda memindahkan backend, update remote config untuk menunjuk ke base URL baru. Pengguna yang sudah ada akan mengambilnya saat peluncuran berikutnya, dengan fallback aman ke nilai yang terakhir di-cache.
Feature flag berguna untuk rollout bertahap, A/B test, kill switch cepat, dan pengujian perubahan berisiko di staging sebelum menyalakannya di prod. Mereka bukan pengganti kontrol keamanan. Jika sebuah flag menjaga sesuatu yang harus dilindungi, itu bukan flag — itu aturan otorisasi.
Perlakukan setiap flag seperti API: nama jelas, pemilik, dan tanggal akhir.
Gunakan nama yang menunjukkan apa yang terjadi saat flag ON, dan bagian produk yang terpengaruh. Skema sederhana:
feature.checkout_new_ui_enabled (fitur ke pelanggan)ops.payments_kill_switch (tombol matikan darurat)exp.search_rerank_v2 (eksperimen)release.api_v3_rollout_pct (rollout bertahap)debug.show_network_logs (diagnostik)Lebih pilih boolean positif (..._enabled) daripada negatif. Pertahankan prefix stabil agar mudah dicari dan diaudit.
Mulai dengan default aman: jika layanan flag down, aplikasi Anda harus berperilaku seperti versi stabil.
Pola realistis: kirim endpoint baru di backend, biarkan yang lama berjalan, dan gunakan release.api_v3_rollout_pct untuk perlahan memindahkan trafik. Jika error naik, kembalikan tanpa hotfix.
Untuk mencegah penumpukan flag:
“Rahasia” adalah apa pun yang menimbulkan kerusakan jika bocor. Pikirkan token API, password database, secret OAuth client, kunci signing (JWT), secret webhook, dan sertifikat privat. Bukan rahasia: API base URL, nomor build, feature flag, atau ID analytics publik.
Pisahkan rahasia dari pengaturan lain. Pengembang harus bisa mengubah konfigurasi aman dengan bebas, sementara rahasia disuntikkan hanya saat runtime dan hanya di tempat yang perlu.
Di dev, simpan rahasia lokal dan mudah dibuang. Gunakan berkas .env atau keychain OS dan buat mudah untuk reset. Jangan komit.
Di staging dan prod, rahasia harus ada di secret store khusus, bukan di repo, bukan di chat, dan tidak dibenamkan ke app mobile.
Rotasi gagal ketika Anda mengganti kunci dan lupa klien lama masih menggunakannya. Rencanakan jendela overlap.
Pendekatan overlap ini bekerja untuk API key, secret webhook, dan kunci signing. Ini menghindari outage mendadak.
Anda punya API staging dan API prod baru. Tujuannya memindahkan trafik bertahap, dengan cara cepat kembali jika ada masalah. Ini lebih mudah bila aplikasi membaca API base URL dari konfigurasi, bukan dari kode.
Perlakukan URL API sebagai nilai deploy-time di semua tempat. Di web app (React), seringkali itu nilai build-time atau file config runtime. Di mobile (Flutter), biasanya flavor ditambah remote config. Di backend (Go), itu env var runtime. Yang penting adalah konsistensi: kode menggunakan satu nama variabel (misalnya, API_BASE_URL) dan tidak pernah memasukkan URL di komponen, service, atau screen.
Rollout bertahap yang aman bisa seperti ini:
Verifikasi terutama tentang menangkap mismatch lebih awal. Sebelum pengguna nyata terkena perubahan, pastikan health endpoint merespons, flow otentikasi bekerja, dan akun test yang sama bisa menyelesaikan satu journey kunci end-to-end.
Sebagian besar bug konfigurasi produksi membosankan: nilai staging tertinggal, default flag terbalik, atau API key hilang di satu region. Pemeriksaan cepat menangkap sebagian besar.
Sebelum deploy, konfirmasikan tiga hal cocok dengan environment target: endpoint, rahasia, dan default.
Lalu lakukan smoke test cepat. Pilih satu alur pengguna nyata dan jalankan end to end, menggunakan instalasi baru atau profil browser bersih agar tidak bergantung token cache.
Kebiasaan praktis: perlakukan staging seperti production dengan nilai berbeda. Itu berarti skema konfigurasi sama, aturan validasi sama, dan bentuk deploy sama. Hanya nilainya yang berbeda.
Kebanyakan outage konfigurasi bukan soal hal rumit. Mereka kesalahan sederhana yang lolos karena konfigurasi tersebar di banyak berkas, langkah build, dan dashboard, dan tak ada yang bisa menjawab: “Nilai apa yang akan dipakai aplikasi sekarang?” Setup yang baik membuat pertanyaan itu mudah dijawab.
Perangkap umum adalah menaruh nilai runtime di tempat build-time. Menyematkan API base URL di build React berarti Anda harus rebuild untuk setiap environment. Lalu seseorang deploy artifact yang salah dan production mengarah ke staging.
Aturan lebih aman: hanya sematkan nilai yang benar-benar tidak berubah setelah rilis (seperti versi app). Simpan detail environment (API URL, switch fitur, endpoint analytics) di runtime bila memungkinkan, dan buat sumber kebenaran jelas.
Ini terjadi ketika default “membantu” tapi tidak aman. Aplikasi mobile mungkin default ke API dev jika tak bisa membaca config, atau backend mungkin fallback ke database lokal jika env var hilang. Itu mengubah kesalahan konfigurasi kecil menjadi outage penuh.
Dua kebiasaan membantu:
Contoh realistis: rilis Jumat malam, dan build production secara tidak sengaja berisi payment key staging. Semua “berfungsi” sampai charge gagal diam-diam. Perbaikannya bukan library pembayaran baru. Itu validasi yang menolak kunci non-prod di production.
Staging yang tidak mirip production memberi keyakinan palsu. Pengaturan database berbeda, background job hilang, atau flag ekstra membuat bug muncul hanya setelah peluncuran.
Jaga staging dekat dengan mencerminkan skema konfigurasi yang sama, aturan validasi yang sama, dan bentuk deploy yang sama. Hanya nilainya yang berbeda, bukan strukturnya.
Tujuannya bukan alat canggih. Tujuannya konsistensi yang membosankan: nama yang sama, tipe yang sama, aturan yang sama di dev, staging, dan prod. Saat konfigurasi bisa diprediksi, rilis tak lagi terasa berisiko.
Mulai dengan menuliskan kontrak konfigurasi yang jelas di satu tempat. Buat singkat tapi spesifik: setiap nama kunci, tipenya (string, number, boolean), dari mana boleh datang (env var, remote config, build-time), dan default-nya. Tambahkan catatan untuk nilai yang tidak boleh diset di client app (seperti private API key). Perlakukan kontrak ini seperti API: perubahan perlu review.
Lalu buat kesalahan gagal lebih awal. Waktu terbaik menemukan API base URL hilang adalah di CI, bukan setelah deploy. Tambahkan validasi otomatis yang memuat config dengan cara yang sama seperti aplikasi Anda dan memeriksa:
Akhirnya, permudah pemulihan saat perubahan konfigurasi salah. Snapshot apa yang berjalan, ubah satu hal pada satu waktu, verifikasi cepat, dan siapkan jalur rollback.
Jika Anda membangun dan mendeloy dengan platform seperti Koder.ai (koder.ai), aturan yang sama berlaku: perlakukan nilai environment sebagai input untuk build dan hosting, jaga rahasia keluar dari source yang diekspor, dan validasi konfigurasi sebelum Anda kirim. Konsistensi itulah yang membuat redeploy dan rollback terasa rutin.
Saat konfigurasi didokumentasikan, divalidasi, dan dapat dibalik, ia berhenti menjadi sumber outage dan berubah menjadi bagian normal dari proses pengiriman.