Node.js Development Environment Setup yang Tidak Bikin Pusing

by Marcus Chen
Node.js Development Environment Setup yang Tidak Bikin Pusing

Banyak engineer buang waktu berjam-jam hanya untuk setup lingkungan kerja, padahal belum satu baris kode bisnis pun ditulis. Lebih parah lagi, setup yang asal-asalan bakal jadi utang teknis yang nagih di kemudian hari: konflik versi Node, node_modules yang corrupt, atau environment dev yang beda jauh dari production.

Ini bukan artikel yang kasih kamu daftar tool tanpa konteks. Ini adalah konfigurasi yang saya pakai sendiri—di mesin macOS dan Linux—untuk proyek-proyek Node.js skala solo hingga tim kecil. Kalau kamu sudah punya setup sendiri, mungkin ada satu-dua bagian yang bisa dicuri.

Kita akan bahas nodejs development environment setup dari nol: manajemen versi, struktur proyek, environment variables, linting, dan opsional Docker untuk paritas dev-production.

Kenapa Manajemen Versi Node Itu Wajib, Bukan Opsional

Install Node langsung dari nodejs.org itu jebakan. Begitu kamu punya dua proyek—satu pakai Node 18, satu butuh Node 20—kamu sudah dalam masalah. Solusinya: nvm (Node Version Manager).

# Install nvm (versi 0.39.7, cek github.com/nvm-sh/nvm untuk terbaru)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# Reload shell
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# Install Node LTS terbaru
nvm install --lts
nvm use --lts
nvm alias default 'lts/*'

Setelah itu, buat file .nvmrc di root setiap proyek:

20.12.2

Lalu tambahkan ini ke .zshrc atau .bashrc supaya versi Node otomatis ganti saat masuk direktori:

autoload -U add-zsh-hook
load-nvmrc() {
  local nvmrc_path
  nvmrc_path="$(nvm_find_nvmrc)"
  if [ -n "$nvmrc_path" ]; then
    local nvmrc_node_version
    nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")") 
    if [ "$nvmrc_node_version" = "N/A" ]; then
      nvm install
    elif [ "$nvmrc_node_version" != "$(nvm version)" ]; then
      nvm use
    fi
  fi
}
add-zsh-hook chpwd load-nvmrc
load-nvmrc

Alternatif untuk yang suka sesuatu yang lebih cepat: fnm (Fast Node Manager, ditulis dalam Rust). Perintahnya mirip nvm tapi startup-nya jauh lebih ngebut. Saya pribadi pakai fnm di mesin baru sejak 2023.

# Install fnm
curl -fsSL https://fnm.vercel.app/install | bash

# Pakai
fnm install 20
fnm use 20
fnm default 20

Struktur Proyek yang Tidak Akan Kamu Sesali

Struktur folder itu opini, tapi ada pola yang terbukti lebih mudah di-maintain. Ini yang saya pakai untuk Express/Fastify API:

my-api/
├── src/
│   ├── config/          # Konfigurasi app (db, env validation)
│   ├── controllers/     # Request handlers
│   ├── services/        # Business logic
│   ├── models/          # Data models / ORM schemas
│   ├── middlewares/     # Express middlewares
│   ├── routes/          # Route definitions
│   └── index.ts         # Entry point
├── tests/
│   ├── unit/
│   └── integration/
├── .env.example
├── .nvmrc
├── .eslintrc.json
├── tsconfig.json
└── package.json

Titik kritis: jangan taruh logic di index.ts. File itu cuma untuk bootstrap—listen port, connect database, register routes. Sisanya masuk services/. Ini yang bikin unit testing tidak menjadi mimpi buruk.

Environment Variables: Jangan Hardcode, Jangan Asal .env

Pakai .env itu benar, tapi validasi environment variables itu yang sering dilupakan. Kalau DATABASE_URL kosong dan app kamu diam-diam jalan dengan nilai undefined, kamu baru tahu masalahnya saat production meledak.

Saya pakai zod untuk validasi env:

npm install zod
// src/config/env.ts
import { z } from 'zod';

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'test', 'production']),
  PORT: z.string().transform(Number).default('3000'),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
});

const parsed = envSchema.safeParse(process.env);

if (!parsed.success) {
  console.error('❌ Invalid environment variables:');
  console.error(parsed.error.flatten().fieldErrors);
  process.exit(1);
}

export const env = parsed.data;

Sekarang kalau ada env yang kurang, app langsung crash dengan pesan yang jelas—bukan error samar di tengah request.

Jangan lupa .env.example di-commit ke repo (tanpa nilai sensitif), dan .env masuk .gitignore:

# .gitignore
.env
.env.local
.env.*.local

Linting dan Formatting: Sekali Setup, Selamanya Konsisten

Debat tabs vs spaces itu buang waktu. Otomasi saja.

ESLint + Prettier masih jadi standar de-facto di 2024. Setup minimal:

npm install -D eslint @eslint/js prettier eslint-config-prettier
// eslint.config.js (flat config, ESLint v9+)
import js from '@eslint/js';
import prettierConfig from 'eslint-config-prettier';

export default [
  js.configs.recommended,
  prettierConfig,
  {
    rules: {
      'no-console': 'warn',
      'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    },
  },
];
// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100
}

Tambahkan pre-commit hook dengan lint-staged dan husky supaya kode jelek tidak pernah masuk repo:

npm install -D husky lint-staged
npx husky init
// package.json
"lint-staged": {
  "*.{js,ts}": ["eslint --fix", "prettier --write"],
  "*.{json,md}": ["prettier --write"]
}
# .husky/pre-commit
npx lint-staged

Satu kali setup, semua commit otomatis rapi. Tidak perlu review nitpick soal formatting lagi.

TypeScript: Aktifkan Strict Mode dari Hari Pertama

Kalau kamu mulai proyek baru, pakai TypeScript dari awal. Migrasi belakangan itu menyakitkan. Dan kalau pakai TypeScript, aktifkan strict: true—jangan setengah-setengah.

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "tests"]
}

Untuk development, pakai tsx sebagai pengganti ts-node—jauh lebih cepat karena tidak butuh type-checking saat run:

npm install -D tsx
// package.json scripts
"scripts": {
  "dev": "tsx watch src/index.ts",
  "build": "tsc",
  "start": "node dist/index.js",
  "lint": "eslint src/",
  "test": "node --experimental-vm-modules node_modules/.bin/jest"
}

Docker untuk Paritas Dev-Production (Opsional tapi Direkomendasikan)

Ini bagian yang sering dilewati solo developer—sampai production berperilaku beda dari dev. Kalau proyek kamu butuh database, Redis, atau service eksternal lain, Docker Compose adalah solusi paling pragmatis.

# docker-compose.yml
services:
  app:
    build:
      context: .
      target: development
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    env_file:
      - .env
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: devuser
      POSTGRES_PASSWORD: devpass
      POSTGRES_DB: myapp_dev
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U devuser"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:
# Dockerfile
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./

FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

FROM base AS production
RUN npm ci --only=production
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]

Dengan setup ini, docker compose up sudah langsung kasih kamu Node app + PostgreSQL dengan hot reload. Tidak perlu install Postgres di mesin lokal, tidak ada konflik port antar proyek.

Kalau kamu belum familiar dengan Docker untuk dev workflow, saya pernah bahas lebih dalam di cara setup Docker untuk development lokal.

Perbandingan Tool: Mana yang Pilih?

Kebutuhan Pilihan A Pilihan B Rekomendasi
Manajemen versi Node nvm fnm fnm (lebih cepat)
TypeScript runner (dev) ts-node tsx tsx
Linting ESLint Biome ESLint (ekosistem lebih matang)
Formatting Prettier Biome Prettier (atau Biome kalau mau all-in-one)
Testing Jest Vitest Vitest (lebih cepat, ESM native)
Package manager npm pnpm pnpm (disk space lebih efisien)

Satu catatan soal Biome: tool ini menjanjikan—linting + formatting dalam satu binary Rust yang cepat. Tapi di pertengahan 2024, plugin ecosystem-nya masih jauh di bawah ESLint. Kalau proyek kamu butuh plugin spesifik (misal eslint-plugin-security), tetap pakai ESLint.

Checklist Setup Akhir

Sebelum mulai coding, pastikan ini sudah ada:

  • .nvmrc atau .node-version di root proyek
  • .env.example dengan semua key yang dibutuhkan
  • .env di .gitignore
  • Validasi env variables saat startup
  • tsconfig.json dengan strict: true
  • ESLint + Prettier terkonfigurasi
  • Pre-commit hook aktif
  • README.md dengan instruksi Getting Started yang jelas

Ini bukan overkill. Ini investasi 30 menit yang menghemat berjam-jam frustrasi di kemudian hari—terutama kalau ada orang lain (atau kamu sendiri 6 bulan ke depan) yang perlu onboarding ke proyek ini.

Kesimpulan

Nodejs development environment setup yang baik bukan soal pakai tool paling baru atau paling keren. Ini soal konsistensi: versi Node terkontrol, environment variables tervalidasi, kode terformat otomatis, dan dev environment semirip mungkin dengan production.

Langkah konkret untuk besok: kalau kamu belum pakai nvm atau fnm, install sekarang dan tambahkan .nvmrc ke semua proyek aktif kamu. Itu saja sudah menyelesaikan 60% masalah "works on my machine".

Kalau kamu mau setup yang lebih lengkap untuk proyek full-stack, cek juga artikel tentang konfigurasi monorepo Node.js dengan pnpm workspaces.