Aplikasi yang sama hampir selalu hidup di lebih dari satu tempat: komputer kita saat mengembangkan, server uji, lalu produksi. Port yang dipakai berbeda, alamat database berbeda, mode debug menyala di satu tempat dan padam di tempat lain. Bila nilai-nilai tersebut ditanam di dalam kode, setiap perpindahan lingkungan menuntut pengeditan — dan satu nilai yang terlupakan mengubah perilaku aplikasi secara diam-diam. Pada chapter ini kita susun arsitektur konfigurasi yang benar: seluruh pengaturan masuk lewat environment variable , divalidasi di satu modul, diberi nilai bawaan yang jelas, lalu dibekukan menjadi satu object untuk seluruh aplikasi.
Konfigurasi yang Tidak Sehat #
Bentuk paling mudah — dan paling rapuh — adalah konstanta yang ditanam langsung di kode:
const PORT = 3000;
const DATABASE_URL = 'file:tugas-lokal.db';
const DEBUG = true;
const BATAS_HALAMAN = 20;
// ... ratusan baris kemudian ...
app.listen(PORT);
db.buka(DATABASE_URL);
if (DEBUG) console.log('permintaan masuk:', url);Daftar masalahnya panjang. Mengganti nilai berarti mengedit kode, dan kode yang berbeda antar lingkungan berarti kita tidak lagi menguji kode yang sama dengan yang dijalankan di produksi. Nilai seperti alamat database yang berisi kredensial ikut tersimpan di version control — pelanggaran yang dibahas tuntas di chapter Manajemen Secret. Dan ketika BATAS_HALAMAN dipakai di lima berkas, menggantinya menjadi pencarian manual yang rawan tertinggal.
Prinsip yang kita kejar berasal dari metodologi twelve-factor: satu kode, banyak lingkungan, perbedaan hidup di environment. Node.js membaca environment melalui process.env — materi dasarnya sudah kita bahas di chapter Environment Variables pada Section Command-Line Applications; chapter ini fokus pada cara menyusunnya menjadi arsitektur.
Satu Modul untuk Seluruh Konfigurasi #
Rencananya sederhana. Seluruh pembacaan process.env dikumpulkan ke satu modul konfigurasi.mjs. Modul tersebut menjalankan tiga tugas berurutan: memvalidasi kunci wajib, menerapkan nilai bawaan untuk kunci opsional, lalu membekukan hasilnya. Kontrak kunci-kuncinya sebagai berikut:
| Kunci | Wajib | Nilai bawaan | Konversi |
|---|---|---|---|
APP_PORT | Ya | — | Number, rentang 1-65535 |
APP_DATABASE_URL | Ya | — | string apa adanya |
APP_DEBUG | Tidak | false | '1' berarti true |
APP_PAGE_SIZE | Tidak | 20 | Number |
Demo: Validasi, Nilai Bawaan, dan Object Beku #
Modul berikut mengekspor satu function bacaKonfigurasi yang menerima object environment sebagai parameter — bukan membaca process.env langsung. Keputusan ini membuat modul dapat diuji dengan kombinasi nilai apa pun tanpa menyentuh environment sungguhan:
// Modul konfigurasi: satu pintu pembaca environment variable.
// Fungsi ini menerima object environment (bukan langsung process.env)
// agar mudah diuji dengan berbagai kombinasi nilai.
export function bacaKonfigurasi(env) {
const galat = [];
const port = Number(env.APP_PORT);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
galat.push('APP_PORT harus berupa bilangan bulat 1-65535');
}
if (env.APP_DATABASE_URL === undefined || env.APP_DATABASE_URL === '') {
galat.push('APP_DATABASE_URL wajib diisi');
}
if (galat.length > 0) {
const e = new Error(`konfigurasi tidak valid: ${galat.join('; ')}`);
e.name = 'KonfigurasiError';
throw e;
}
return Object.freeze({
port,
urlDatabase: env.APP_DATABASE_URL,
modeDebug: env.APP_DEBUG === '1',
batasHalaman: Number(env.APP_PAGE_SIZE ?? 20),
});
}Sekarang kita uji kontraknya dengan kombinasi yang mewakili: kunci lengkap, kunci opsional kosong, percobaan mutasi, dan tiga keadaan tidak valid:
import { bacaKonfigurasi } from './konfigurasi.mjs';
const lengkap = bacaKonfigurasi({
APP_PORT: '3000',
APP_DATABASE_URL: 'file:tugas.db',
APP_DEBUG: '1',
APP_PAGE_SIZE: '50',
});
console.log('semua kunci terisi :', JSON.stringify(lengkap));
const tanpaOpsional = bacaKonfigurasi({
APP_PORT: '3000',
APP_DATABASE_URL: 'file:tugas.db',
});
console.log('nilai bawaan :', JSON.stringify(tanpaOpsional));
try {
lengkap.port = 8080;
} catch (e) {
console.log('mutasi ditolak :', e.name);
}
const kasusGagal = [
['port kosong', { APP_DATABASE_URL: 'file:tugas.db' }],
['port bukan angka', { APP_PORT: 'delapan ribu', APP_DATABASE_URL: 'file:tugas.db' }],
['url database hilang', { APP_PORT: '3000' }],
];
for (const [label, env] of kasusGagal) {
try {
bacaKonfigurasi(env);
} catch (e) {
console.log(`${label.padEnd(18)} -> ${e.name}: ${e.message}`);
}
}Output yang Dihasilkan:
semua kunci terisi : {"port":3000,"urlDatabase":"file:tugas.db","modeDebug":true,"batasHalaman":50}
nilai bawaan : {"port":3000,"urlDatabase":"file:tugas.db","modeDebug":false,"batasHalaman":20}
mutasi ditolak : TypeError
port kosong -> KonfigurasiError: konfigurasi tidak valid: APP_PORT harus berupa bilangan bulat 1-65535
port bukan angka -> KonfigurasiError: konfigurasi tidak valid: APP_PORT harus berupa bilangan bulat 1-65535
url database hilang -> KonfigurasiError: konfigurasi tidak valid: APP_DATABASE_URL wajib diisiMari kita bedah perilaku yang terbukti di output:
- Baris
semua kunci terisi: String'3000'dan'50'dikonversi menjadi angka;'1'padaAPP_DEBUGmenjaditrue. Bentuk akhirnya object tunggal — konsumen tinggal membacakonfig.port, bukan memilah string dari environment. - Baris
nilai bawaan: TanpaAPP_DEBUGdanAPP_PAGE_SIZE, konfigurasi tetap sah —modeDebugberupafalsedanbatasHalamanberupa 20. Operator??di baris terakhirkonfigurasi.mjshanya mengganti nilaiundefined; nilai lain yang sengaja diisi tetap diteruskan apa adanya. - Baris
mutasi ditolak:Object.freezemembuat object konfigurasi menjadi baca-saja; percobaan mengubahportmelemparTypeError. Konfigurasi yang tidak bisa berubah setelah dibangun berarti tidak ada modul yang bisa diam-diam mengubah pengaturan di tengah jalan. - Tiga baris terakhir: Seluruh kegagalan terkumpul sekaligus —
KonfigurasiErrormembawa daftar lengkap, bukan hanya keluhan pertama. Port kosong dan port berupa teks sama-sama ditolak oleh pemeriksaanNumber.isInteger, dan pesan kesalahan menyebut kunci yang dimaksud sehingga pengoperasi langsung tahu apa yang harus diperbaiki.
Menyala dari Environment Asli #
Titik masuk aplikasi memanggil bacaKonfigurasi(process.env) dalam satu tempat. Bila konfigurasi tidak valid, aplikasi berhenti lebih dulu — gagal cepat, sebelum server sempat mendengarkan port atau koneksi database dibuka:
import { bacaKonfigurasi } from './konfigurasi.mjs';
try {
const konfig = bacaKonfigurasi(process.env);
console.log(`aplikasi tugas siap di port ${konfig.port}`);
console.log(`database : ${konfig.urlDatabase}`);
console.log(`mode debug : ${konfig.modeDebug ? 'aktif' : 'nonaktif'}`);
console.log(`batas halaman: ${konfig.batasHalaman}`);
} catch (e) {
console.error(`${e.name}: ${e.message}`);
process.exitCode = 1;
}Jalankan dengan environment lengkap di depan perintah:
APP_PORT=3000 APP_DATABASE_URL=file:tugas.db APP_DEBUG=1 node mulai-aplikasi.mjsOutput yang Dihasilkan:
aplikasi tugas siap di port 3000
database : file:tugas.db
mode debug : aktif
batas halaman: 20Lalu tanpa environment apa pun — keadaan yang meniru operator lupa menyiapkan lingkungan:
node mulai-aplikasi.mjsOutput yang Dihasilkan:
KonfigurasiError: konfigurasi tidak valid: APP_PORT harus berupa bilangan bulat 1-65535; APP_DATABASE_URL wajib diisi
kode keluar: 1Dua output tersebut menceritakan dua ujung arsitektur. Dengan environment lengkap, aplikasi menyala dengan seluruh nilai terkonversi dan tercetak rapi. Tanpa environment, bacaKonfigurasi mengumpulkan dua kelalaian sekaligus (port dan database), pesan kesalahan menjelaskan keduanya, dan proses berakhir dengan kode keluar 1 — sinyal standar bagi sistem operasi dan platform deployment bahwa proses gagal.
Konfigurasi per Lingkungan #
Dengan arsitektur ini, membedakan lingkungan menjadi urusan environment, bukan kode. Lingkungan pengembangan menyalakan APP_DEBUG=1 dan memakai database lokal; lingkungan produksi mematikan debug dan menyuntikkan nilai lewat platform deployment. Untuk kenyamanan harian, flag node --env-file memuat pasangan kunci-nilai dari sebuah berkas — mekanisme flag-nya dibahas khusus di chapter Berkas Environment dengan –env-file pada Section Modern Node.js, termasuk aturan bahwa nilai yang sudah terpasang di environment menang atas nilai di berkas. Perkakas serupa dibahas di panduan environment variable Node.js
, dan urusan penyimpanan kredensial dengan aman menjadi ranah chapter Manajemen Secret.
Object konfigurasi beku kini siap disuntikkan ke seluruh lapisan aplikasi. Pertanyaan berikutnya bersifat fisik: di folder mana semua modul ini sebaiknya berdiri — konfigurasi.mjs, service, repository, rute, beserta tes-nya? Chapter Struktur Proyek yang Skalabel merancang tata letaknya.