Kode yang tertata dalam lapisan tetap perlu rumah fisik: berkas layanan.mjs harus tinggal di folder yang bisa ditemukan tanpa menebak. Tata letak folder menentukan pengalaman membuka proyek enam bulan kemudian — apakah pengembang yang menambah fitur baru langsung tahu ke mana harus melihat, atau harus menyusuri berkas bernama utils-lain.mjs. Pada chapter ini kita merancang tata letak untuk aplikasi Node.js yang bertumbuh: struktur dasar berlapis, alternatif berbasis fitur, masalah impor relatif yang dalam, dan prinsip yang menjaga struktur tetap sehat ketika jumlah kontributor bertambah.

Tata Letak Dasar #

Aplikasi berlapis dari chapter-chapter sebelumnya secara alami memetakan dirinya ke folder. Bentuk paling umum untuk aplikasi menengah terlihat seperti ini:

text
aplikasi-tugas/
├── package.json          # {"type": "module"}
├── .env                  # konfigurasi lokal - tidak masuk version control
├── .gitignore
├── src/
│   ├── server.mjs        # titik masuk: merakit seluruh lapisan
│   ├── konfigurasi.mjs   # modul konfigurasi dari chapter sebelumnya
│   ├── routes/           # lapisan presentasi
│   │   └── tugas.mjs
│   ├── services/         # lapisan aturan bisnis
│   │   └── tugas.mjs
│   └── repositories/     # lapisan data
│       ├── tugas-memori.mjs
│       └── tugas-sqlite.mjs
└── tests/
    ├── tugas-service.test.mjs
    └── tugas-repository.test.mjs

Setiap tempat punya peran yang jelas:

LokasiIsiCatatan
src/server.mjsTitik masuk; memanggil factory, menyuntikkan dependensiSatu tempat melihat seluruh perakitan
src/konfigurasi.mjsPembacaan environment tervalidasiDikonsumsi lewat parameter, bukan import global
src/routes/Handler dan perkakas HTTPSatu berkas per resource atau kelompok rute
src/services/Aturan bisnis murniTidak mengimpor node:http
src/repositories/Implementasi kontrak dataVariasi per media simpan di berkas terpisah
tests/Berkas uji node:testMeniru nama berkas yang diuji

Dua detail kecil pada tabel itu besar dampaknya. Pertama, server.mjs menjadi satu-satunya berkas yang mengenal semua lapisan sekaligus — membacanya sama dengan membaca diagram arsitektur dalam bentuk kode. Kedua, dua implementasi repository (tugas-memori.mjs dan tugas-sqlite.mjs) berdampingan sebagai berkas setara, bukan sebagai kode bercampur — kontrak dari chapter Pola Repository terlihat langsung dari nama berkasnya.

Berlapis atau Berbasis Fitur #

Struktur berlapis di atas bekerja baik ketika jumlah resource masih sekitar satu tangan. Ketika proyek menampung belasan fitur — tugas, pengguna, tag, notifikasi, laporan — folder services/ menampung 30 berkas yang saling tidak berhubungan, dan satu perubahan fitur menuntut melompat ke empat folder berbeda. Alternatifnya adalah menata berdasarkan fitur (feature-based): seluruh berkas satu fitur berkumpul dalam satu folder.

text
aplikasi-tugas/
├── package.json
├── src/
│   ├── server.mjs
│   ├── konfigurasi.mjs
│   └── fitur/
│       ├── tugas/
│       │   ├── rute.mjs
│       │   ├── layanan.mjs
│       │   ├── repo-memori.mjs
│       │   └── repo-sqlite.mjs
│       └── pengguna/
│           ├── rute.mjs
│           ├── layanan.mjs
│           └── repo.mjs
└── tests/
    └── fitur/
        ├── tugas.test.mjs
        └── pengguna.test.mjs
AspekBerlapis (layer-based)Berbasis fitur (feature-based)
Menemukan seluruh kode satu fiturMelompat antar folderSatu folder berisi semua
Melihat semua handler / semua serviceSatu folder berisi semuaMelompat antar fitur
Cocok untukAplikasi menengah, tim kecilAplikasi besar, banyak tim paralel
Risiko saat membesarFolder lapisan membengkakKode bersama antar fitur kabur

Tidak ada pemenang mutlak. Keduanya menjaga arah dependensi yang sama — rute.mjs memanggil layanan.mjs, layanan.mjs memanggil repo*.mjs; yang berubah hanyalah tempat berkas tinggal. Sebagian proyek memakai keduanya sekaligus: folder fitur/ untuk domain, folder services/ kecil untuk layanan lintas fitur seperti pencatatan audit.

Impor Relatif dan Kedalaman Folder #

Semakin dalam folder, semakin panjang jalur impor relatif. Berkas di kedalaman tiga folder yang membutuhkan konfigurasi.mjs di src/ menulis:

js
// dari src/fitur/tugas/rute.mjs
import { konfig } from '../../../konfigurasi.mjs';

Tiga tingkat titik-titik bukan sekadar tidak sedap dipandang: jumlah .. harus dihitung ulang setiap kali berkas berpindah kedalaman, dan satu tingkat yang keliru membuat resolusi menunjuk ke berkas yang salah tanpa pesan yang jelas. Dokumentasi import specifiers menggolongkan '../../../konfigurasi.mjs' sebagai relative specifier — jalur yang ditafsirkan relatif terhadap lokasi berkas pengimpor, itulah sumber rapuhnya.

Ada dua penawar yang saling melengkapi. Yang pertama struktural: jaga kedalaman tetap rendah — struktur fitur di atas hanya dua tingkat dari src/. Yang kedua bawaan Node.js: field "imports" di package.json memetakan nama berawalan # ke jalur di dalam package, sehingga import tidak lagi bergantung pada kedalaman berkas:

json
{
  "name": "aplikasi-tugas",
  "type": "module",
  "imports": {
    "#konfigurasi": "./src/konfigurasi.mjs",
    "#services/*": "./src/services/*.mjs"
  }
}

Dengan pemetaan itu, berkas di kedalaman mana pun cukup menulis import { konfig } from '#konfigurasi'; — satu tempat definisi, nol titik-titik. Kunci # sengaja diwajibkan agar nama internal tidak bentrok dengan nama package di npm.

Prinsip Penataan yang Menjaga Skalabilitas #

Terlepas dari bentuk yang dipilih, beberapa prinsip menjaga struktur tetap hidup saat proyek membesar:

  1. Titik masuk tunggal — hanya server.mjs (atau cli.mjs) yang merakit lapisan; berkas lain tidak saling mengimpor secara lintas lapisan dari jauh.
  2. Colocation — berkas yang berubah bersama diletakkan berdekatan; tes untuk satu berkas diberi nama serupa di tests/.
  3. Tes meniru struktur src/ — ketika tests/fitur/tugas.test.mjs gagal, kita langsung tahu domain mana yang bermasalah tanpa membuka berkasnya.
  4. Nama folder mengungkap arsitektur — routes/, services/, repositories/ di folder adalah bab berjalan dari section ini.
  5. Struktur tumbuh, bukan ditebak — mulai dari berkas datar di src/, pecah ke folder ketika jumlah berkas dan pemiliknya bertambah.

Struktur Proyek pada Course Ini #

Lima proyek pada bagian akhir Course ini memakai pola yang sama sehingga kebiasaan kita terbawa langsung ke sana:

text
content/nodejs/proyek/rest-api-manajemen-tugas/
├── _index.md            # halaman overview proyek
├── package.json         # {"type": "module"}
├── bagian/              # halaman tutorial per tahap pengerjaan
├── src/                 # kode lengkap proyek (ESM)
└── tests/               # berkas uji node:test (*.test.js)

Folder src/ menampung kode produksi, tests/ menampung berkas uji, dan package.json dengan "type": "module" membuat seluruh berkas .js di dalam proyek otomatis diperlakukan sebagai ES Modules — konvensi yang kita kenal sejak Section Modules dan Packages. Pengujian dijalankan dengan node --test dari akar proyek tanpa argumen; pada Node 24, test runner menemukan berkas uji secara otomatis, dan argumen direktori justru tidak didukung.

Struktur folder yang rapi menutup rangkaian Section ini: aplikasi kita kini tertata dari dalam (lapisan dan dependensi) sampai ke luar (folder dan berkas). Struktur yang sama kini menjadi pijakan untuk memikirkan ukuran berikutnya — bukan jumlah berkas, melainkan jumlah permintaan; Section Performance dan Scalability melanjutkan dari sini.