Environment & Config — Node.js

Environment variables memisahkan konfigurasi dari kode — penting untuk keamanan dan fleksibilitas. process.env — akses environment variables…

Config adalah nilai yang beda antara laptopmu dan server: port, alamat database, secret untuk JWT, mode aplikasi. Nilai-nilai itu gak ditulis di kode, tapi diberikan dari luar lewat environment variable: pasangan NAMA=nilai yang diterima program saat dijalankan. Bayangin rumah makan yang punya banyak cabang. Buku resepnya sama dan difotokopi ke semua cabang. Alamat cabang, nomor telepon, dan kunci gudang ditulis di kartu yang ditempel di dapur masing-masing, bukan di buku resep. Buku resep itu kodemu di git, kartunya adalah environment tiap mesin, dan kunci gudang adalah secret yang gak boleh ikut difotokopi.

🧪 Tebakan cepat: .env berisi PORT=3000, dan kodenya const port = process.env.PORT + 1;. Apa isi port? Tebak dulu, jawabannya ada di bagian "Nilainya selalu teks".

process.env: nilai dari luar kode

Di Node, semua environment variable ada di object process.env. Di app.js milik catatan-api:

const port = Number(process.env.PORT) || 3000;
app.listen(port, () => {
  console.log(`Server jalan di http://localhost:${port}`);
});
$ node app.js
Server jalan di http://localhost:3000
$ PORT=8080 node app.js
Server jalan di http://localhost:8080

Nilainya selalu teks

Isi process.env selalu string, sekalipun kelihatan seperti angka atau boolean:

Kode Hasil
process.env.PORT "3000"
process.env.PORT + 1 "30001"
Number(process.env.PORT) + 1 3001
process.env.DEBUG dengan DEBUG=false "false", dan di if dianggap true

Itu jawaban tebakannya: "30001". Tanda + menyambung teks, bukan menjumlah. Ubah dulu dengan Number(...), dan bandingkan boolean secara eksplisit: process.env.DEBUG === "true".

File .env dan tiga cara memuatnya

Mengetik semua variabel di depan node tiap kali itu repot. Saat develop, nilainya ditaruh di file .env di root proyek:

# .env catatan-api (jangan di-commit)
NODE_ENV=development
PORT=3000
DATABASE_URL=postgres://budi:rahasia@localhost:5432/catatan

Node gak membaca file ini sendiri. Ada tiga cara memuatnya ke process.env:

  1. node --env-file=.env app.js: bawaan Node 20.6 ke atas, tanpa install apa-apa.
  2. process.loadEnvFile(); di baris paling atas: bawaan Node 20.12 ke atas, membaca .env di folder tempat kamu menjalankan node.
  3. Package dotenv: npm install dotenv, lalu require("dotenv").config(); di baris paling atas. Ini cara klasik yang jalan di versi Node mana pun.

Ketiganya memakai format yang sama: satu NAMA=nilai per baris, # untuk komentar, tanda kutip boleh untuk nilai yang ada spasinya.

Siapa yang menang: shell atau .env

Variabel yang sudah ada di environment menang atas isi file, baik dengan --env-file maupun dotenv:

$ PORT=8080 node --env-file=.env app.js
Server jalan di http://localhost:8080

.env bilang 3000, shell bilang 8080, dan yang dipakai 8080. Ini sengaja: di server, nilai yang diset hosting atau systemd gak tertimpa file yang kebetulan ikut ter-upload.

NODE_ENV: development atau production

NODE_ENV adalah nama yang disepakati bersama untuk mode aplikasi:

Satu file config, dicek saat start

Baca semua variabel di satu file, dan tolak start kalau ada yang wajib tapi kosong:

// config.js
const wajib = ["DATABASE_URL", "JWT_SECRET"];
const kurang = wajib.filter((nama) => !process.env[nama]);
if (kurang.length > 0) {
  console.error(`Config error: ${kurang.join(", ")} belum diset. Salin .env.example jadi .env, lalu isi.`);
  process.exit(1);
}

module.exports = {
  env: process.env.NODE_ENV || "development",
  port: Number(process.env.PORT) || 3000,
  databaseUrl: process.env.DATABASE_URL,
  jwtSecret: process.env.JWT_SECRET,
};

Dengan .env di atas (belum ada JWT_SECRET):

$ node --env-file=.env app.js
Config error: JWT_SECRET belum diset. Salin .env.example jadi .env, lalu isi.
$ echo $?
1

Secret gak pernah masuk git

# .env.example (di-commit)
NODE_ENV=development
PORT=3000
DATABASE_URL=
JWT_SECRET=

Di latihan: config per environment

Kode awal sudah mengisi process.env.PORT = "3000" dan object config dengan development (port 3000, logLevel: "debug") dan production (port 8080, logLevel: "warn"). Kamu menulis getConfig(env) yang mengembalikan config[env] || config.development, dan validateEnv(required) yang memakai filter dengan !process.env[key], lalu mengembalikan { valid: missing.length === 0, missing }. Di simulator latihan, DATABASE_URL belum diset, jadi baris terakhirnya Env check: {"valid":false,"missing":["DATABASE_URL"]}.

⚠️ Jebakan yang sering ketemu

TL;DR: Nilai yang beda antar mesin (port, database, secret, mode) dibaca dari process.env, bukan ditulis di kode. Isinya selalu string: process.env.PORT + 1 memberi "30001", jadi pakai Number(...). File .env dimuat dengan node --env-file=.env app.js (Node 20.6+), process.loadEnvFile(), atau package dotenv, dan variabel yang sudah ada di environment menang atas file. NODE_ENV=production mengubah perilaku Express dan npm. Baca config di satu file, cek yang wajib saat start, lalu process.exit(1) dengan pesan jelas (fail fast). .env di .gitignore, .env.example yang di-commit, dan secret gak pernah ada di kode.

Yang akan kamu pelajari