Menulis semua kode sendiri dari nol bukanlah cara kerja ekosistem Node.js. Kekuatan runtime ini justru terletak pada npm — package manager yang menyediakan lebih dari dua juta package siap pakai, dari web framework sampai alat pemformatan kode. npm terpasang otomatis bersama Node.js, dan seluruh aktivitasnya berporos pada dua hal yang telah kita kenal: berkas package.json sebagai catatan resmi, dan folder node_modules sebagai gudang berkas terpasang. Chapter ini membahas siklus hidup dependencies: memasang, mencatat, melepas, dan menjaga instalasi tetap terulang sama di setiap mesin.

npm dalam Sekilas Pandang #

Istilah npm merujuk pada dua hal sekaligus: registry — server publik tempat jutaan package dipublikasikan — dan npm CLI, program baris perintah yang terpasang bersama Node.js. CLI inilah yang membaca package.json, mengunduh package dari registry, menuliskannya ke node_modules, dan mencatat perubahan kembali ke package.json. Karena registry bersifat daring, contoh perintah pemasangan pada chapter ini ditampilkan sebagai ilustrasi — pembaca menjalankannya sendiri di proyek masing-masing.

Memasang Package dengan npm install #

Perintah inti npm adalah npm install. Dengan satu argument nama package, perintah tersebut memasang package terbaru dan mencatatnya ke package.json:

bash
npm install express

Satu perintah ini mengubah tiga tempat sekaligus. Field dependencies di package.json mendapat entri baru, misalnya "express": "^5.1.0". Folder node_modules di akar proyek terisi berkas package beserta seluruh dependencies-nya. Dan berkas package-lock.json diperbarui dengan versi persis yang terpasang. Tabel berikut merangkum variasi yang paling sering dipakai:

PerintahEfek
npm installMemasang seluruh dependencies yang tercatat di package.json
npm install expressMemasang package express lalu mencatatnya ke dependencies
npm install [email protected]Memasang versi tertentu secara eksplisit
npm install -D vitestMemasang package sebagai devDependencies
npm uninstall expressMelepas package dari node_modules dan mencatat penghapusannya

Tanpa argument, npm install membaca catatan yang ada dan memastikan node_modules lengkap — inilah perintah pertama yang kita jalankan setelah menyalin proyek orang lain. Prefix -D adalah bentuk pendek dari --save-dev; keduanya sama persis.

dependencies dan devDependencies #

Kedua field ini sama-sama berisi daftar package, tetapi membedakan kapan package dibutuhkan. Field dependencies menampung package yang dipakai aplikasi saat berjalan di produksi — tanpa package tersebut aplikasi tidak bisa menyala. Field devDependencies menampung package yang hanya dipakai selama pengembangan: test runner, linter, pemformat kode.

PackageSebaiknya masukAlasan
expressdependenciesAplikasi memanggil framework ini saat melayani permintaan
vitestdevDependenciesPengujian hanya dijalankan saat mengembangkan
prettierdevDependenciesPemformatan kode terjadi sebelum kode dikirim
dotenvdependenciesAplikasi membaca berkas .env saat berjalan

Pemisahan ini berbuah nyata saat deploy: perintah npm install --omit=dev di server produksi melewati seluruh devDependencies, sehingga server tidak mengunduh test runner yang tidak pernah dipakainya. Kesalahan kategori tidak sampai merusak aplikasi — hanya membuat instalasi produksi membengkak, atau sebaliknya membuat instalasi tim baru kehilangan tool wajib.

Memakai Dependency yang Telah Terpasang #

Setelah terpasang, package diimpor dengan specifier bare — dan di sinilah algoritma resolusi dari Chapter sebelumnya bekerja untuk kita. Berkas berikut memuat express dan menyusun satu route tanpa menjalankan server:

js
import express from "express";

const aplikasi = express();
aplikasi.get("/tugas", (permintaan, jawaban) => {
  jawaban.json({ status: "siap" });
});

console.log("tipe ekspor express :", typeof express);
console.log("tipe aplikasi        :", typeof aplikasi);
console.log("tipe aplikasi.get    :", typeof aplikasi.get);

Output yang Dihasilkan:

text
tipe ekspor express : function
tipe aplikasi        : function
tipe aplikasi.get    : function

Mari kita telaah baris-baris kode di atas:

  1. Baris 1 (import express from "express"): specifier bare "express" memicu pencarian node_modules berjenjang. Bundle chapter ini berada di dalam proyek yang menyimpan express di node_modules pada akarnya, sehingga impor berhasil tanpa instalasi tambahan.
  2. Baris 3 (const aplikasi = express()): memanggil express menghasilkan aplikasi — object function yang menjadi pusat konfigurasi web server.
  3. Baris 4-6 (aplikasi.get(...)): satu route didaftarkan dengan callback dua parameter. Callback tidak dijalankan di sini karena server belum menyala; baris ini hanya membuktikan bahwa seluruh API express telah siap dipakai.

Kode aplikasi tidak pernah menuliskan versi atau lokasi berkas — cukup nama package, dan algoritma resolusi mengurus sisanya.

package-lock.json Menjaga Keberulangan #

package.json mencatat rentang versi yang boleh dipakai, misalnya ^5.1.0, tetapi rentang itu mengizinkan banyak versi berbeda. Dua pengembang yang menjalankan npm install pada hari berbeda bisa saja mendapat versi express yang berbeda — dan bug halus muncul di satu mesin saja. Berkas package-lock.json hadir untuk menutup celah tersebut dengan mencatat versi persis dari setiap package, termasuk dependencies dari dependencies:

json package-lock.json (potongan yang disederhanakan)
{
  "name": "aplikasi-catatan",
  "version": "0.1.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "node_modules/express": {
      "version": "5.1.0",
      "resolved": "<URL berkas tarball di registry npm>",
      "integrity": "sha512-..."
    }
  }
}

Entry "resolved" mencatat URL unduhan persis dari registry dan "integrity" menyimpan sidik jari kriptografis isi berkas — perintah npm ci memakai kedua nilai tersebut untuk memasang ulang instalasi yang identik bit demi bit. Berbeda dengan node_modules, package-lock.json justru wajib ikut tersimpan di repositori Git agar setiap anggota tim dan server produksi memperoleh pohon dependencies yang sama persis.

Anatomi node_modules #

Folder node_modules menyimpan berkas asli setiap package yang terpasang, satu subfolder per nama package, tepat seperti yang kita jumpai pada demo resolusi:

text
node_modules/
├── express/
│   ├── package.json
│   ├── index.js
│   └── node_modules/     ← dependencies milik express sendiri
├── merge-descriptors/
│   └── package.json
└── ...

Ketika package yang kita pasang turut membutuhkan package lain, npm menempatkan semuanya di tingkat atas folder ini — teknik yang disebut hoisting — agar satu salinan dapat dipakai bersama dan pencarian berjenjang menemukannya. Versi yang bentrok ditempatkan bersarang di node_modules milik package yang membutuhkannya. Folder ini memang cepat membesar berisi puluhan ribu berkas, dan itulah mengapa node_modules selalu dibangun ulang dari package.json dan package-lock.json, bukan disalin manual.

Instalasi kini utuh: package.json mencatat kebutuhan, package-lock.json mengunci versi, node_modules menyimpan berkas. Satu notasi belum kita bedah — tanda ^ di depan "express": "^5.1.0" beserta angka tiga tingkat 5.1.0. Notasi itulah bahasa versi seluruh ekosistem, dan Chapter Semantic Versioning menguraikannya tuntas.