Belajar ExpressJS - Configuration & Environment
Episode 16 of 28

Belajar ExpressJS - Configuration & Environment

Mengelola konfigurasi aplikasi Express dengan dotenv, env vars, config terpusat per environment, validasi konfigurasi dengan zod, dan praktik menyimpan secret tanpa pernah commit.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Sejauh ini beberapa kode kita memakai process.env.PORT, process.env.JWT_SECRET, dan process.env.DATABASE_URL — tapi nilainya belum diatur secara rapi. Episode 16 merapikan lapisan konfigurasi: dari mana nilai itu dibaca, bagaimana diatur per environment, dan bagaimana memastikan tidak ada secret yang bocor ke git.

Mengapa ini penting? Karena konfigurasi yang berantakan adalah akar dua masalah besar: misconfigurasi (port/URL salah di environment tertentu) dan kebocoran secret (key ter-commit, lalu database dibobol). Konfigurasi terpusat yang divalidasi sejak startup menangkap kedua masalah ini lebih awal — sebelum kode dijalankan.

dotenv dan File .env

Alur Kerja Dasar

Install dotenv
npm install dotenv
JSMemuat env di entry point
import "dotenv/config"
import app from "./app.js"
 
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`Server berjalan di http://localhost:${PORT}`)
})

Import "dotenv/config" di baris pertama entry point memuat file .env ke process.env. Aturan praktiknya: load sedini mungkin agar seluruh modul lain bisa membaca env saat di-import.

File-File Env

FilePeranStatus git
.envNilai lokal development❌ tidak pernah commit
.env.exampleTemplate nama variabel (tanpa nilai rahasia)✅ commit
.env.productionOverride untuk produksi (jarang; biasanya via platform)

.env.example adalah kontrak: ia memberi tahu rekan tim variabel apa yang harus mereka isi tanpa pernah mengekspos nilai rahasia.

.env.example - template tanpa rahasia
PORT=3000
NODE_ENV=development
MONGODB_URI=mongodb://localhost:27017/expresslab
DATABASE_URL=postgres://dev:dev@localhost:5432/expresslab
JWT_SECRET=ganti-dengan-random-string-panjang
SESSION_SECRET=ganti-dengan-random-string-panjang
CORS_ORIGIN=http://localhost:5173

Warning

Satu secret yang ter-commit bisa memicu kebocoran akun dan audit keamanan. Periksa git status sebelum commit dan pastikan .env ada di .gitignore (kita tambahkan di episode 3). Jika pernah ter-commit ke git, rotasi secret itu — anggap sudah bocor selamanya.

Config Terpusat

Satu Modul, Semua Konfigurasi

Alih-alih membaca process.env.X tersebar di seluruh kode, kumpulkan dalam satu modul. Hasilnya: satu tempat untuk memeriksa, mendokumentasikan, dan memvalidasi seluruh konfigurasi.

JSsrc/config.js - config terpusat
import "dotenv/config"
 
export const config = {
  env: process.env.NODE_ENV || "development",
  port: Number(process.env.PORT) || 3000,
  mongodbUri: process.env.MONGODB_URI,
  databaseUrl: process.env.DATABASE_URL,
  jwtSecret: process.env.JWT_SECRET,
  corsOrigins: (process.env.CORS_ORIGIN || "").split(",").filter(Boolean),
  isProduction: process.env.NODE_ENV === "production",
  isDevelopment: process.env.NODE_ENV !== "production",
}

Manfaatnya langsung terasa:

  • Konsistensi tipeport sudah Number, corsOrigins sudah array, bukan string mentah.
  • Satu titik dokumentasi — membaca config.js sama dengan membaca seluruh pengaturan aplikasi.
  • Mudah diuji — nilai bisa di-override tanpa menyentuh env sungguhan.

Validasi Konfigurasi

Gagal Cepat di Startup

Konfigurasi yang salah sebaiknya menggagalkan startup, bukan berjalan dengan nilai kosong dan meledak di tengah request. Zod dari episode 13 kembali berjasa:

JSsrc/config.js - validasi dengan zod
import "dotenv/config"
import { z } from "zod"
 
const envSchema = z.object({
  NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
  PORT: z.coerce.number().int().min(1).max(65535).default(3000),
  MONGODB_URI: z.string().url(),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32, "JWT_SECRET minimal 32 karakter"),
  SESSION_SECRET: z.string().min(32, "SESSION_SECRET minimal 32 karakter"),
  CORS_ORIGIN: z.string().default("http://localhost:5173"),
})
 
const parsed = envSchema.safeParse(process.env)
 
if (!parsed.success) {
  console.error("Konfigurasi environment tidak valid:")
  console.error(parsed.error.issues.map((i) => `  ${i.path} - ${i.message}`).join("\n"))
  process.exit(1)
}
 
export const config = {
  env: parsed.data.NODE_ENV,
  port: parsed.data.PORT,
  mongodbUri: parsed.data.MONGODB_URI,
  databaseUrl: parsed.data.DATABASE_URL,
  jwtSecret: parsed.data.JWT_SECRET,
  corsOrigins: parsed.data.CORS_ORIGIN.split(",").filter(Boolean),
  isProduction: parsed.data.NODE_ENV === "production",
}

Dua perilaku yang dihasilkan:

  • JWT_SECRET dengan panjang di bawah 32 karakter ditolak saat startup — bukan saat user login.
  • Env yang kurang lengkap (misal MONGODB_URI hilang) menghentikan server dengan pesan yang jelas, bukan request 500 misterius.

Per Environment

Konfigurasi bisa berbeda per environment — kuncinya tetap pada satu config.js:

JSPerilaku berbeda per environment
export const config = {
  env: parsed.data.NODE_ENV,
  isProduction: parsed.data.NODE_ENV === "production",
  isDevelopment: parsed.data.NODE_ENV !== "production",
 
  logging: {
    level: parsed.data.NODE_ENV === "production" ? "info" : "debug",
  },
 
  security: {
    cookieSecure: parsed.data.NODE_ENV === "production",
    trustProxy: parsed.data.NODE_ENV === "production",
  },
}

Pola ini menghubungkan semua keputusan ke NODE_ENV yang konsisten: cookie secure hanya di produksi (episode 12), level log lebih verbose di development (episode 17), dan trust proxy aktif saat di belakang load balancer (episode 25).

Tip

Jangan pernah menempatkan logika "produksi atau tidak" di dalam route — taruh di config.js. Ketika kebijakan berubah (misal cookie kini perlu secure di staging juga), kalian cukup mengubah satu file, bukan mengejar string NODE_ENV di seluruh codebase.

Common Pitfalls

Proses Env Tidak Terisi

Env dari docker run -e atau platform cloud tidak otomatis masuk ke .env — dan file .env tidak ter-bundle di image. Pastikan nilai dikirim eksplisit: -e di Docker, env vars di platform (episode 24).

NODE_ENV Salah Ketik

"production" vs "prod" — satu typo mengubah perilaku cookie dan logging. Validasi dengan z.enum di atas mencegah ini sejak startup.

Menggabungkan .env ke Dalam Image

Jangan pernah menyalin .env ke Docker image. Platform menyuntikkan env saat runtime. Kita praktikkan langsung di episode 24.

Important

Rotasi secret adalah prosedur wajib jika secret pernah bocor — sekalipun hanya diduga. Jangan menunda: ganti JWT_SECRET/SESSION_SECRET, invalidasi token lama, dan terapkan prinsip assume breach.

Penutup

Episode 16 merapikan lapisan konfigurasi: dotenv memuat .env, config.js menjadi satu sumber kebenaran, zod memvalidasi seluruh env di startup, dan .env.example menjadi kontrak tim tanpa membocorkan rahasia.

Inti yang harus dibawa pulang:

  • import "dotenv/config" di baris pertama entry point.
  • Kumpulkan semua process.env di satu config.js dengan tipe yang konsisten.
  • Gagal cepat di startup saat env tidak valid (zod safeParse + process.exit(1)).
  • .env.example di-commit; .env dan nilai rahasia tidak pernah.
  • Kebijakan per environment (cookie secure, log level, trust proxy) dipusatkan di config.
  • Rotasi secret saat ada indikasi kebocoran.

Di episode 17 selanjutnya kita akan membuat aplikasi bisa diselidiki: logging & debugging — morgan untuk request log, pino untuk structured logging, dan alur debugging dengan Node inspector. Sampai jumpa di episode 17!

Belajar ExpressJS - Configuration & Environment | Belajar ExpressJS