Aplikasi yang berjalan mulus di komputer kita belum tentu siap dijalankan di server. Lingkungan produksi berbeda dalam banyak hal: tidak ada yang mengetik perintah secara manual setiap kali aplikasi harus menyala, tidak ada layar terminal yang bisa kita pantau sepanjang hari, dan kesalahan kecil langsung terasa oleh pengguna sungguhan. Deployment adalah proses memindahkan aplikasi ke lingkungan semacam itu — dan seperti pekerjaan lainnya, hasilnya ditentukan oleh persiapan. Pada chapter ini kita petakan apa saja yang berubah dari pengembangan ke produksi, rapikan dependencies, atur NODE_ENV, dan siapkan titik pemeriksaan kesehatan.

Apa yang Berubah dari Pengembangan ke Produksi #

Mari kita mulai dengan membandingkan dua lingkungan tersebut secara berdampingan:

AspekDi pengembanganDi produksi
Prosesdijalankan manual dari terminaldikelola process manager atau container
Dependencieslengkap termasuk alat ujihanya yang dibutuhkan aplikasi berjalan
Konfigurasiberkas .env lokal, nilai longgarenvironment variable dari platform, tervalidasi
Detail galatpesan kesalahan lengkap tampil di responsdetail hanya ke log, klien menerima pesan umum
Penyalaan ulangkita menekan Ctrl+C lalu menjalankan lagiotomatis oleh sistem saat proses berhenti

Perhatikan polanya: semua perbedaan itu bukan soal kode yang ditulis ulang, melainkan soal kode yang sudah siap menerima perbedaan. Aplikasi yang membaca konfigurasi dari environment — seperti yang kita rancang pada chapter Konfigurasi Aplikasi — tidak perlu diubah sedikit pun ketika pindah lingkungan; yang berubah hanyalah nilai yang disuntikkan platform. Sisa dari chapter ini membahas persiapan yang belum kita bahas sebelumnya.

Memisahkan dependencies dari devDependencies #

package.json membagi dependencies ke dalam dua kelompok. Field dependencies berisi package yang dibutuhkan aplikasi saat berjalan — server web, driver database, validasi input. Field devDependencies berisi package yang hanya dibutuhkan selama pengembangan — test runner, linter, formatter. Pemisahan ini penting di produksi karena alat uji tidak pernah ikut berjalan di server, tetapi tetap memakan ruang dan waktu instalasi bila terpasang.

Alat uji dipasang ke kelompok yang benar dengan flag --save-dev:

bash
npm install --save-dev vitest

Saat membangun di server, kita memasang hanya kelompok pertama dengan perintah npm ci beserta flag --omit=dev:

bash
npm ci --omit=dev

Perintah npm ci berbeda dari npm install dalam satu hal penting: npm ci membaca package-lock.json lalu memasang versi yang tercatat di sana secara persis, tanpa menyelesaikan ulang rentang versi. Hasilnya bisa direproduksi — server memasang bit-for-bit package yang sama dengan yang kita uji di komputer. Perilaku lengkap perintah ini, termasuk syarat keberadaan lockfile, terdokumentasi di referensi npm ci .

NODE_ENV Bukan Pengaturan Bawaan Node.js #

Banyak materi di luar sana menyebut NODE_ENV=production seolah merupakan tombol rahasia yang membuat Node.js berjalan lebih cepat. Kenyataannya lebih sederhana: inti Node.js tidak membaca variabel NODE_ENV sama sekali. Variabel ini murni konvensi lintas ekosistem — framework dan library yang membacanya, bukan runtime. Express, misalnya, memeriksa NODE_ENV untuk memilih perilaku produksi: menyembunyikan detail stack trace pada halaman galat dan menyalakan cache tampilan. Praktik ini terdokumentasi di panduan praktik terbaik Express .

Karena itu langkah kita sederhana: set variabel ini di lingkungan produksi agar framework yang membacanya berperilaku sesuai, dan jangan berharap efek lain muncul dengan sendirinya.

bash
NODE_ENV=production node server.mjs

Titik Pemeriksaan Kesehatan #

Platform deployment modern memantau aplikasi lewat satu pertanyaan berulang: “apakah kamu masih sehat?” Pertanyaan itu diajukan sebagai HTTP request ke sebuah endpoint khusus — biasanya /health — dan dijawab semurah mungkin: status 200 tanpa menyentuh database. Bila jawaban gagal berulang, platform menandai aplikasi tidak sehat, berhenti mengirim traffic, lalu menyalakan ulang prosesnya. Berkas berikut memperlihatkan bentuk paling sederhananya:

js
import http from 'node:http';

// PORT dibaca dari environment; nilai 0 memakai port ephemeral
// agar contoh ini bisa berjalan dan berhenti dengan rapi.
const port = Number(process.env.PORT ?? 0);

const server = http.createServer((req, res) => {
  if (req.url === '/health') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ status: 'ok' }));
    return;
  }
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ tugas: ['belajar node', 'latihan deploy'] }));
});

server.listen(port, '127.0.0.1', async () => {
  const alamat = `http://127.0.0.1:${server.address().port}`;
  for (const jalur of ['/', '/health']) {
    const r = await fetch(`${alamat}${jalur}`);
    console.log(`${jalur.padEnd(8)} -> ${r.status} ${await r.text()}`);
  }
  server.close();
});

Output yang Dihasilkan:

text
/        -> 200 {"tugas":["belajar node","latihan deploy"]}
/health  -> 200 {"status":"ok"}

Mari kita bedah bagian-bagian pentingnya:

  1. Baris 5 (const port = ...): port dibaca dari environment variable PORT — konvensi yang diikuti hampir semua platform, karena merekalah yang menentukan port mana yang terbuka. Nilai cadangan 0 memakai port ephemeral sehingga contoh ini bisa menguji dirinya sendiri; di server sungguhan, PORT selalu terisi dari platform.
  2. Baris 8-12 (cabang /health): endpoint ini sengaja tidak menyentuh database atau membaca berkas — pemeriksaan kesehatan harus murah karena dipanggil berkali-kali setiap menit oleh platform dan penyeimbang beban.
  3. Baris 16-22 (pengujian mandiri): server mendengarkan port ephemeral, mengirim request ke rute utama dan rute /health memakai fetch, mencetak status beserta body, lalu menutup dirinya — pola yang membuat contoh Course ini bisa dijalankan dan selesai dengan rapi.

Satu kebiasaan lagi yang perlu dibiasakan: platform menjalankan aplikasi lewat satu perintah standar. Berkas package.json di bundle chapter ini mendefinisikan script start:

json
{
  "name": "aplikasi-tugas",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node server-kesehatan.mjs"
  },
  "dependencies": {},
  "devDependencies": {}
}

Script tersebut dipanggil dengan npm start:

bash
npm start

Output yang Dihasilkan:

text

> [email protected] start
> node server-kesehatan.mjs

/        -> 200 {"tugas":["belajar node","latihan deploy"]}
/health  -> 200 {"status":"ok"}

Field dependencies dan devDependencies sengaja dibiarkan kosong karena contoh ini hanya memakai module inti — bentuk lengkap dengan dependencies nyata terlihat pada proyek-proyek Section Proyek. Banner [email protected] start menandakan npm menjalankan script yang tepat; di baliknya, perintah node server-kesehatan.mjs berjalan persis seperti sebelumnya.

Daftar Periksa Sebelum Naik ke Server #

Sebelum aplikasi dikirim, hal-hal berikut sudah seharusnya beres:

  • package-lock.json ter-commit dan npm ci --omit=dev berhasil dijalankan dari keadaan bersih.
  • Seluruh konfigurasi dibaca dari environment variable — tidak ada alamat database atau secret yang tertanam di kode (urusan penyimpanannya dibahas di chapter Manajemen Secret).
  • Test dan lint lolos di komputer lokal — dan nanti di pipeline, topik Chapter CI/CD dengan GitHub Actions.
  • Endpoint /health merespons cepat tanpa efek samping.
  • Aplikasi membaca PORT dari environment, bukan angka yang ditulis tetap di kode.

Dengan daftar itu terpenuhi, aplikasi kita siap dipindahkan. Persiapan berikutnya menyoal bagaimana aplikasi berperilaku setelah menyala di lingkungan asing: konfigurasi produksi yang tepat — port, timeout, proxy, dan penyembunyian detail galat — topik Chapter Konfigurasi Produksi.