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:
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.mjsSetiap tempat punya peran yang jelas:
| Lokasi | Isi | Catatan |
|---|---|---|
src/server.mjs | Titik masuk; memanggil factory, menyuntikkan dependensi | Satu tempat melihat seluruh perakitan |
src/konfigurasi.mjs | Pembacaan environment tervalidasi | Dikonsumsi lewat parameter, bukan import global |
src/routes/ | Handler dan perkakas HTTP | Satu berkas per resource atau kelompok rute |
src/services/ | Aturan bisnis murni | Tidak mengimpor node:http |
src/repositories/ | Implementasi kontrak data | Variasi per media simpan di berkas terpisah |
tests/ | Berkas uji node:test | Meniru 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.
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| Aspek | Berlapis (layer-based) | Berbasis fitur (feature-based) |
|---|---|---|
| Menemukan seluruh kode satu fitur | Melompat antar folder | Satu folder berisi semua |
| Melihat semua handler / semua service | Satu folder berisi semua | Melompat antar fitur |
| Cocok untuk | Aplikasi menengah, tim kecil | Aplikasi besar, banyak tim paralel |
| Risiko saat membesar | Folder lapisan membengkak | Kode 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:
// 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:
{
"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:
- Titik masuk tunggal — hanya
server.mjs(ataucli.mjs) yang merakit lapisan; berkas lain tidak saling mengimpor secara lintas lapisan dari jauh. - Colocation — berkas yang berubah bersama diletakkan berdekatan; tes untuk satu berkas diberi nama serupa di
tests/. - Tes meniru struktur
src/— ketikatests/fitur/tugas.test.mjsgagal, kita langsung tahu domain mana yang bermasalah tanpa membuka berkasnya. - Nama folder mengungkap arsitektur —
routes/,services/,repositories/di folder adalah bab berjalan dari section ini. - 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:
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.