Konfigurasi dan rahasia

Berkas env, konfigurasi per lingkungan, dan disiplin rahasia.

Daftar isi

Konfigurasi aplikasi Node mencakup nilai yang berbeda per lingkungan, seperti porta server, tingkat log, dan string koneksi basis data, serta nilai yang bersifat rahasia seperti kunci API dan kata sandi. Konfigurasi dipisahkan dari kode sumber dengan memasoknya lewat variabel lingkungan, sehingga kode tetap sama antara lingkungan dev, staging, dan produksi, sedangkan nilai yang berbeda dipasok dari luar. Setelah membaca topik ini, pembaca dapat membaca konfigurasi dari process.env, menyusunnya menjadi objek terstruktur dengan nilai default dan konversi tipe, memuat berkas .env pada lingkungan pengembangan, dan menjaga rahasia tetap di luar kontrol versi.

Berkas env dan pola twelve-factor#

Variabel lingkungan adalah sarana konfigurasi utama yang memisahkan nilai dari kode sumber. Node memasok seluruh variabel lingkungan proses melalui objek global process.env, dan nilainya selalu bertipe string atau undefined. Konsekuensi tipe ini adalah konversi eksplisit diperlukan untuk angka dan boolean. String "8080" tidak dapat dipakai langsung sebagai porta pada server.listen yang meminta angka, dan string "false" tetap dianggap true bila dievaluasi sebagai boolean sebab string tidak kosong selalu truthy.

Berkas .env berisi pasangan NAMA=nilai dan lazim dipakai pada lingkungan pengembangan agar pembaca tidak perlu mengetik variabel lingkungan pada setiap pemanggilan node. Berkas ini tidak dimuat otomatis oleh Node, sehingga memerlukan paket seperti dotenv atau fitur bawaan Node sejak versi 20.6 yang memuat .env dengan opsi --env-file. Pada produksi, berkas .env jarang dipakai karena nilai konfigurasi dipasok oleh runner proses, orchestrator kontainer, atau layanan manajemen rahasia, bukan oleh berkas yang ikut masuk paket deploy.

env .env.example
NODE_ENV=development
APP_PORT=3000
APP_LOG_LEVEL=info
APP_DB_URL=postgres://localhost:5432/app_dev
APP_JWT_SECRET=ganti-dengan-string-acak-yang-panjang

Berkas .env.example di atas berfungsi sebagai dokumentasi variabel yang diperlukan aplikasi, tanpa memuat nilai rahasia. Berkas ini aman dimasukkan ke kontrol versi agar pengembang baru tahu variabel apa yang perlu dipasok. Berkas .env yang berisi nilai asli tidak boleh masuk kontrol versi, sebab memuat kredensial yang berlaku pada lingkungan tertentu.

Konfigurasi sebagai objek terstruktur#

Membaca process.env langsung di banyak tempat pada kode membuat aplikasi sulit diuji dan mudah membocorkan nilai default yang berbeda antar modul. Praktik yang lebih disiplin adalah membaca seluruh variabel lingkungan sekali pada modul konfigurasi tunggal, lalu mengekspor objek terstruktur yang dipakai modul lain. Modul ini juga tempat untuk menetapkan nilai default, melakukan konversi tipe, dan menandai flag seperti isProduction yang diturunkan dari NODE_ENV.

js config.js
const config = {
  port: Number(process.env.APP_PORT) || 3000,
  nodeEnv: process.env.NODE_ENV || 'development',
  logLevel: process.env.APP_LOG_LEVEL || 'info',
  isProduction: process.env.NODE_ENV === 'production',
  dbUrl: process.env.APP_DB_URL || 'postgres://localhost:5432/app_dev'
};

console.log('konfigurasi:');
console.log(JSON.stringify(config, null, 2));
text Keluaran
konfigurasi:
{
  "port": 3000,
  "nodeEnv": "development",
  "logLevel": "info",
  "isProduction": false,
  "dbUrl": "postgres://localhost:5432/app_dev"
}

Keluaran di atas dihasilkan ketika skrip dijalankan tanpa variabel lingkungan tambahan, sehingga seluruh nilai default dipakai. Konversi Number(process.env.APP_PORT) menghasilkan angka karena APP_PORT tidak diset dan Number(undefined) mengembalikan NaN, yang oleh operator || dianggap falsy sehingga diganti dengan 3000. Untuk memeriksa perilaku ketika variabel lingkungan dipasok, jalankan skrip yang sama dengan variabel yang eksplisit.

bash
# jalankan dengan konfigurasi produksi
APP_PORT=8080 NODE_ENV=production APP_LOG_LEVEL=debug \
APP_DB_URL=postgres://db.local:5432/app_prod node config.js
text Keluaran
konfigurasi:
{
  "port": 8080,
  "nodeEnv": "production",
  "logLevel": "debug",
  "isProduction": true,
  "dbUrl": "postgres://db.local:5432/app_prod"
}

Variabel yang dipasok pada awal perintah menggantikan seluruh nilai default, sehingga port menjadi 8080 dan isProduction menjadi true karena NODE_ENV bernilai 'production'. Pola ini menunjukkan bahwa kode sumber tidak perlu diubah ketika konfigurasi berubah, sebab perbedaan ditangani oleh variabel lingkungan yang dipasok dari luar.

Memuat berkas env dengan dotenv#

Pada lingkungan pengembangan, mengetik variabel lingkungan pada setiap pemanggilan node menjadi repitisi yang melelahkan. Paket dotenv memuat berkas .env pada direktori kerja dan menempatkan pasangannya ke process.env sebelum kode aplikasi berjalan. Pemanggilan require('dotenv').config() cukup dilakukan sekali pada titik masuk aplikasi, biasanya berkas src/index.js atau server.js. Modul yang diimpor setelah pemanggilan ini langsung melihat variabel lingkungan yang baru, sehingga urutan pemuatan menjadi penting dan modul dotenv harus menjadi impor pertama pada berkas titik masuk.

Sejak Node 20.6, opsi --env-file=.env pada CLI node memuat berkas .env tanpa paket tambahan. Opsi ini lebih disukai pada proyek baru karena mengurangi dependensi dan menempatkan pemuat env pada level runtime, sehingga kode aplikasi tidak perlu tahu bahwa berkas .env dipakai. Opsi --env-file juga mendukung beberapa berkas dengan urutan eksplisit, yang berguna untuk memisahkan .env umum dan .env.local yang berisi penggantian per pengembang. Variabel yang dideklarasikan pada berkas pada urutan awal dapat ditimpa oleh berkas pada urutan belakang, sehingga pengembang dapat menimpa nilai tim tanpa mengubah berkas bersama.

Disiplin rahasia#

Rahasia seperti kunci API, kata sandi basis data, dan kunci penandatanganan token harus tetap berada di variabel lingkungan dan tidak boleh ditanam pada kode sumber, dicetak ke log, atau ditulis ke berkas yang masuk kontrol versi. Disiplin ini mencegah kebocoran ketika kode sumber dibagikan, diunggah ke registry paket, atau diinspeksi pada alat pelacakan error.

Daftar berikut merangkum praktik yang menjaga rahasia tetap terisolasi:

  • Berkas .env yang berisi nilai asli ditambahkan ke .gitignore, sedangkan .env.example tanpa nilai rahasia dimasukkan ke kontrol versi sebagai dokumentasi.
  • Log aplikasi tidak mencetak objek konfigurasi secara penuh, sebab console.log(config) dapat membocorkan dbUrl dan kunci lainnya ke alat pengumpul log.
  • Pesan error yang dibangkitkan dari kegagalan koneksi atau autentikasi tidak menyertakan string koneksi atau kata sandi pada konteksnya.
  • Variabel rahasia dipasok ke proses anak sebagai variabel lingkungan eksplisit, bukan sebagai argumen baris perintah, sebab argumen baris perintah terlihat pada ps aux dan alat sejenis.

Kesalahan umum#

Penanganan boolean dari variabel lingkungan adalah sumber bug yang sering muncul. Ekspresi if (process.env.APP_DEBUG) menganggap string kosong sebagai false tetapi string 'false' sebagai true, sebab string non-kosong selalu truthy pada JavaScript. Pembandingan eksplisit seperti process.env.APP_DEBUG === '1' memberikan hasil yang sesuai harapan dan tidak ambigu. Variabel yang memerlukan konversi boolean sebaiknya dibaca pada modul konfigurasi dengan pembandingan eksplisit, sehingga modul lain menerima boolean asli dan tidak perlu mengulang konversi.

Kesalahan lain adalah menempatkan nilai default yang berbeda antar modul. Modul server.js mungkin memakai process.env.APP_PORT || 3000, sedangkan modul client.js memakai process.env.APP_PORT || 4000. Akibatnya, klien menghubungi porta 4000 padahal server mendengarkan porta 3000. Pemusatan pembacaan variabel lingkungan pada modul konfigurasi tunggal menghilangkan inkonsistensi ini, sebab nilai default hanya ditetapkan sekali dan dibagikan ke seluruh modul yang membutuhkan.

Ringkasan#

  • Variabel lingkungan dipasok lewat process.env sebagai string atau undefined, sehingga konversi ke angka memakai Number(...) dan ke boolean memakai pembandingan eksplisit seperti === 'production'.
  • Konfigurasi dipusatkan pada satu modul yang membaca process.env, menetapkan nilai default, dan mengekspor objek terstruktur agar modul lain tidak perlu membaca variabel lingkungan secara langsung.
  • Berkas .env dipakai pada lingkungan pengembangan dan dimuat lewat paket dotenv atau opsi --env-file pada Node 20.6 ke atas, sedangkan produksi memasok variabel dari runner proses atau layanan manajemen rahasia.
  • Rahasia tidak boleh ditanam pada kode sumber, dicetak ke log, atau dimasukkan ke kontrol versi, dan .env yang berisi nilai asli ditambahkan ke .gitignore.

Referensi#