Secret adalah nilai yang membuka akses: API key, password database, kunci untuk menandatangani token. Tempatnya bukan di kode. Bayangin PIN kartu ATM: kamu gak menuliskannya di kartu, karena kartunya dibawa ke mana-mana dan bisa difotokopi. Kode juga begitu. Dia disalin ke git, ke laptop teman setim, ke server CI, bahkan sebagian ke browser setiap pengunjung. Jadi secret disimpan terpisah, di environment variable (variabel yang disediakan sistem saat program jalan), dan kode cuma menyebut namanya.
🧪 Tebakan cepat: di proyek Vite kamu menulis
VITE_STRIPE_SECRET_KEY=sk_live_abc123di.env, dan.envsudah masuk.gitignore. Apakah secret itu aman? Tebak dulu, jawabannya ada di bagian "Prefix VITE_ dan NEXT_PUBLIC_ artinya publik".
Kode membaca nama, nilainya dari luar
// BURUK: secret ditulis di kode, ikut ter-commit ke git
const API_KEY = "sk-abc123xyz";
// BAIK: kode cuma tahu namanya, nilainya disediakan saat program jalan
const API_KEY = process.env.OPENAI_API_KEY;
process.envadalah object di Node.js yang berisi semua environment variable.process.env.OPENAI_API_KEYmembaca satu nilai berdasarkan namanya, dan hasilnyaundefinedkalau belum diset.- Begitu baris pertama masuk commit, secret itu ada di git history di setiap clone repo. Menghapusnya di commit berikutnya gak cukup.
Saat develop, nilainya ditaruh di file .env di root proyek:
# .env (cuma di laptopmu, jangan di-commit)
STRIPE_SECRET_KEY=sk_live_abc123
OPENAI_API_KEY=sk-abc123xyz
Node 20.6 ke atas bisa membacanya langsung dengan node --env-file=.env server.js; di versi lama pakai package dotenv. Di server produksi, nilainya diisi lewat pengaturan hosting atau secret manager, bukan lewat file yang dikirim bareng kode.
.gitignore dan .env.example
# .gitignore
node_modules
dist
.env
# .env.example (di-commit: daftar nama, tanpa nilai)
STRIPE_SECRET_KEY=
OPENAI_API_KEY=
.envdi.gitignoremembuat git gak pernah menawarkan file itu untuk di-commit..env.examplememberi tahu teman setim variabel apa saja yang harus mereka isi, tanpa membocorkan nilainya.
Kalau .env sudah terlanjur ter-commit, .gitignore gak menolong: file yang sudah dilacak git tetap dilacak. Keluarkan dari pelacakan dengan git rm --cached .env, commit, lalu ganti (rotate) semua key di dalamnya, karena nilai lamanya tetap ada di history.
Prefix VITE_ dan NEXT_PUBLIC_ artinya publik
Di Vite, cuma variabel berawalan VITE_ yang bisa dibaca kode frontend lewat import.meta.env. Saat npm run build, Vite menempelkan nilainya sebagai teks biasa ke file JavaScript yang diunduh setiap pengunjung. Next.js melakukan hal yang sama untuk NEXT_PUBLIC_. Ini proyek yang ada di Lab:
// src/checkout.js (jalan di browser)
fetch("https://api.stripe.com/v1/payment_intents", {
method: "POST",
headers: { Authorization: `Bearer ${import.meta.env.VITE_STRIPE_SECRET_KEY}` },
});
$ npm run build
$ grep -rnoE "sk_live_[0-9A-Za-z]+" dist/
dist/assets/index-D7nb3Uqz.js:1:sk_live_abc123
Secret-nya ada di bundle dan bisa dibaca siapa pun lewat DevTools. Itu jawaban tebakannya: gak aman. .gitignore cuma menjaga file .env dari git, bukan dari build.
Perbaikannya: secret gak boleh pakai prefix itu, dan panggilan yang butuh secret pindah ke server. Browser memanggil server-mu sendiri, dan server yang memegang key:
// src/checkout.js (browser): panggil server sendiri
fetch(import.meta.env.VITE_API_URL + "/pay", { method: "POST", body: JSON.stringify({ orderId }) });
// server/pay.js (server): secret tetap di server
fetch("https://api.stripe.com/v1/payment_intents", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}` },
});
Yang memang boleh publik: URL API (VITE_API_URL), Stripe publishable key (pk_live_...), Supabase anon key. Yang gak boleh: apa pun dengan kata secret, private, password, atau service role di namanya.
Laravel: env() di config, config() di kode
Di Laravel, env('API_KEY') membaca environment variable dari .env. Aturannya: panggil env() cuma di file dalam folder config/, lalu kode lain membaca lewat config():
// config/services.php
'openai' => ['key' => env('OPENAI_API_KEY')],
// di controller
$key = config('services.openai.key');
Setelah php artisan config:cache (biasa dijalankan saat deploy), .env gak dibaca lagi, dan env() di luar folder config/ mengembalikan null.
Cek dulu sebelum jalan: validateEnv
Server yang jalan dengan variabel kosong baru error saat variabel itu dipakai, misalnya di tengah pembayaran. Lebih baik dicek begitu server mulai:
function validateEnv(requiredVars) {
const missing = requiredVars.filter((name) => !process.env[name]);
return { valid: missing.length === 0, missing };
}
process.env.DB_HOST = "localhost";
process.env.DB_PASSWORD = "secret";
console.log("All set:", JSON.stringify(validateEnv(["DB_HOST", "DB_PASSWORD"])));
console.log("Missing API_KEY:", JSON.stringify(validateEnv(["DB_HOST", "DB_PASSWORD", "API_KEY"])));
All set: {"valid":true,"missing":[]}
Missing API_KEY: {"valid":false,"missing":["API_KEY"]}
filtermemeriksa setiap nama dan menyimpan yang belum punya nilai.!process.env[name]bernilaitrueuntukundefineddan string kosong"", jadi variabel yang diset kosong juga dihitung hilang.validbernilaitruekalaumissingkosong.JSON.stringifymengubah object jadi teks JSON, dengan tanda kutip di nama properti:{"valid":true,"missing":[]}. Tanpa itu,console.logdi Node mencetak{ valid: true, missing: [] }.
⚠️ Jebakan yang sering ketemu
- Mengira
.gitignoremenghapus file yang sudah ter-commit. Jalankangit rm --cached .env, lalu rotate key-nya. - Secret dengan prefix
VITE_atauNEXT_PUBLIC_. Nilainya masuk bundle browser. Pindahkan panggilannya ke server. env()di controller Laravel. Setelahconfig:cache, hasilnyanull. Taruhenv()di fileconfig/, baca denganconfig().- Mencetak hasil
validateEnvtanpaJSON.stringify. Output-nya jadi{ valid: true, missing: [] }tanpa tanda kutip, bukan{"valid":true,"missing":[]}.
TL;DR: Kode cuma menyebut nama secret (process.env.OPENAI_API_KEY, config('services.openai.key') di Laravel), nilainya datang dari environment. Simpan nilai lokal di .env yang masuk .gitignore, commit .env.example tanpa nilai, dan kalau .env sempat ter-commit, jalankan git rm --cached .env lalu rotate semua key-nya. Prefix VITE_ dan NEXT_PUBLIC_ artinya nilainya ditempel ke bundle browser, jadi secret disimpan tanpa prefix dan dipakai di server. Cek variabel wajib saat server mulai dengan filter dan !process.env[name], lalu cetak dengan JSON.stringify.