Pemilihan proyek

Layanan API dengan persistensi dan autentikasi yang nyata.

Daftar isi

Topik ini memilih satu proyek yang menjadi konteks pembahasan tiga topik berikutnya, yaitu layanan API catatan pribadi dengan autentikasi JWT dan persistensi Prisma. Topik ini memilih ranah yang netral, mendaftarkan sumber daya dan fungsionalitas inti, serta merancang struktur direktori dan ketergantungan sebelum kode ditulis. Setelah membaca topik ini, lingkup proyek dapat dijelaskan kepada orang lain, sumber daya dapat didaftarkan tanpa ambiguitas, dan struktur direktori awal dapat dibuat sebagai fondasi tiga topik berikutnya.

Lingkup proyek yang tepat#

Layanan catatan pribadi dipilih sebagai proyek penutup jalur Node.js sebab ranahnya netral dan non-komersial, namun fungsionalitas intinya tidak main-main. Layanan ini menyentuh persistensi data pada basis data relasional, autentikasi berbasis token, validasi masukan dengan schema, pengujian end-to-end, kontainerisasi Docker, dan dokumentasi publik. Setiap komponen ini sudah dibahas pada modul sebelumnya, sehingga tugas modul pengerjaan proyek adalah integrasi, bukan pengenalan konsep baru.

Ranah catatan pribadi dipilih alih-alih ranah lain sebab dua pertimbangan. Pertama, model datanya sederhana, yaitu satu pengguna memiliki banyak catatan, sehingga skema basis data tetap ramping dan kueri tidak melibatkan banyak join. Kedua, autentikasi menjadi kebutuhan asli, bukan tempelan, sebab setiap catatan milik satu pengguna dan tidak boleh dibaca oleh pengguna lain. Dengan demikian, JWT dan middleware otorisasi menjadi bagian yang mengikat, bukan dekoratif.

Proyek ini menjaga tiga batasan. Pertama, tidak ada fitur berbagi catatan antar-pengguna, sehingga otorisasi tetap satu arah dan tidak memerlukan kebijakan akses berbagi (sharing policy). Kedua, tidak ada pencarian teks penuh (full-text search), sehingga kueri pencarian tetap berupa ILIKE sederhana pada kolom judul. Ketiga, tidak ada berkas lampiran, sehingga unggah berkas dan penyimpanan objek tidak masuk lingkup. Tiga batasan ini menjaga proyek tetap dapat diselesaikan sebagai portofolio tanpa kehilangan karakter layanan yang nyata.

Sumber daya dan fungsionalitas inti#

Layanan ini memiliki dua sumber daya utama, yaitu pengguna dan catatan, dengan relasi satu-ke-banyak. Satu pengguna memiliki nol atau lebih catatan, sedangkan satu catatan dimiliki tepat oleh satu pengguna. Relasi ini diwujudkan pada basis data sebagai kolom penggunaId pada tabel catatan yang merujuk kolom id pada tabel pengguna. Endpoint publik terbatas pada registrasi dan login, sedangkan seluruh endpoint catatan terlindungi token JWT yang membawa klaim penggunaId.

Daftar fungsionalitas inti disusun sebelum kode ditulis agar implementasi tinggal mengikuti peta yang sudah ditetapkan. Fungsionalitas tersebut mencakup registrasi pengguna dengan hashing kata sandi memakai bcrypt, login yang mengembalikan JWT, pembuatan catatan baru, pembacaan daftar catatan milik pengguna terotentikasi, pembacaan satu catatan berdasarkan id, pembaruan catatan milik pengguna terotentikasi, dan penghapusan catatan. Endpoint publik dan terlindungi dipisahkan secara eksplisit pada berkas rute.

Sumber dayaEndpoint publikEndpoint terlindungi
PenggunaPOST /auth/register, POST /auth/logintidak ada
Catatantidak adaGET /catatan, POST /catatan, GET /catatan/:id, PUT /catatan/:id, DELETE /catatan/:id

Pemisahan endpoint publik dan terlindungi mengarah pada struktur middleware yang jelas. Middleware auth yang memverifikasi JWT dipasang hanya pada rute catatan, sedangkan rute registrasi dan login menerima masukan mentah. Penambahan sumber daya baru di kemudian hari, misalnya arsip tautan, mengikuti pola yang sama, yaitu endpoint publik untuk operasi yang tidak butuh identitas pengguna dan endpoint terlindungi untuk operasi yang butuh klaim penggunaId.

Struktur direktori dan ketergantungan#

Struktur direktori dirancang agar setiap berkas memiliki satu tanggung jawab. Berkas app.js membuat instance Express dan memasang middleware, sedangkan server.js memuat app.js dan menjalankan app.listen. Pemisahan ini memungkinkan pengujian memuat app tanpa membuka socket. Direktori routes/ berisi definisi rute, controllers/ berisi handler, services/ berisi logika domain, schemas/ berisi schema zod, dan middleware/ berisi middleware yang dapat dipakai ulang. Berkas prisma/schema.prisma menyatakan skema basis data sebagai sumber kebenaran.

text struktur-proyek.txt
// tidak-dijalankan: pohon direktori, bukan berkas yang dieksekusi
catatan-api/
├── package.json
├── .env.example
├── .gitignore
├── prisma/
│   ├── schema.prisma
│   └── migrations/
├── src/
│   ├── app.js
│   ├── server.js
│   ├── config/
│   │   └── env.js
│   ├── middleware/
│   │   ├── auth.js
│   │   ├── error.js
│   │   └── log.js
│   ├── routes/
│   │   ├── auth.js
│   │   ├── catatan.js
│   │   └── index.js
│   ├── controllers/
│   │   ├── auth.js
│   │   └── catatan.js
│   ├── services/
│   │   ├── auth.js
│   │   └── catatan.js
│   ├── schemas/
│   │   ├── auth.js
│   │   └── catatan.js
│   └── prisma/
│       └── client.js
└── test/
    ├── auth.test.js
    └── catatan.test.js

Direktori schemas/ sengaja dipisahkan dari controllers/ agar schema dapat dipakai ulang pada pengujian dan pada dokumentasi. Schema yang sama yang memvalidasi req.body pada endpoint POST /catatan juga dapat dipakai pada pengujian untuk memverifikasi bahwa data hasil validasi memenuhi kontrak. Pemisahan ini menjaga satu sumber kebenaran untuk bentuk setiap sumber daya, sehingga perubahan aturan, misalnya menambah panjang minimum judul, cukup dilakukan pada satu berkas.

Ketergantungan dipilih dengan alasan yang dapat dijelaskan. express dipilih sebagai framework HTTP sebab ekosistemnya matang dan middleware-nya komposabel. @prisma/client dan prisma dipilih sebagai ORM sebab berkas schema.prisma yang terpisah dari kode aplikasi memudahkan pembacaan skema. zod dipilih untuk validasi sebab safeParse mengembalikan hasil terstruktur tanpa melempar exception. bcrypt dipilih untuk hashing kata sandi sebab algoritma ini sudah lazim dan sengaja dibuat lambat. jsonwebtoken dipilih untuk JWT sebab pustaka ini menjadi standar de facto di ekosistem Node. pino dipilih untuk logging sebab keluarannya berstruktur JSON dan throughput-nya tinggi. supertest menjadi dependensi dev sebab pustaka ini memungkinkan pengujian HTTP tanpa membuka socket sungguhan.

json package.json
{
  "name": "catatan-api",
  "version": "1.0.0",
  "description": "Layanan API catatan pribadi dengan autentikasi JWT dan persistensi Prisma.",
  "type": "commonjs",
  "main": "src/server.js",
  "scripts": {
    "start": "node src/server.js",
    "dev": "node --watch src/server.js",
    "test": "node --test test/",
    "migrate": "prisma migrate dev",
    "studio": "prisma studio"
  },
  "dependencies": {
    "@prisma/client": "^6.0.0",
    "bcrypt": "^6.0.0",
    "express": "^5.0.0",
    "jsonwebtoken": "^9.0.0",
    "pino": "^9.0.0",
    "zod": "^3.25.0"
  },
  "devDependencies": {
    "prisma": "^6.0.0",
    "supertest": "^7.0.0"
  },
  "engines": {
    "node": ">=22.0.0"
  }
}

Berkas package.json di atas menyatakan type: "commonjs", sehingga seluruh berkas JavaScript memakai require dan module.exports. Pilihan ini menjaga konsistensi dengan modul-modul sebelumnya pada jalur ini yang juga memakai CommonJS. Skrip test memakai node --test, sehingga pengujian tidak memerlukan dependensi runner tambahan. Skrip migrate menjalankan prisma migrate dev yang membaca schema.prisma dan menghasilkan berkas migrasi SQL.

Schema validasi sebagai kontrak awal#

Sebelum kode Express ditulis, schema zod untuk sumber daya catatan dapat ditulis sebagai kontrak awal. Schema ini menyatakan bahwa setiap catatan memiliki judul berupa string antara 3 sampai 120 karakter, isi berupa string tidak kosong, serta tag berupa larik string dengan maksimum lima elemen. Schema yang sama akan dipasang pada middleware validasi pada topik implementasi, sehingga kontrak yang ditulis di sini menjadi kode yang berjalan di sana.

js validasi-proyek.js
// tidak-dijalankan: eksekusi delegasi ke blok bash berikut dengan NODE_PATH ke node_modules zod
const { z } = require("zod");

const SkemaCatatan = z.object({
  judul: z
    .string()
    .min(3, "Judul minimal 3 karakter")
    .max(120, "Judul maksimal 120 karakter"),
  isi: z.string().min(1, "Isi tidak boleh kosong"),
  tag: z.array(z.string()).max(5, "Maksimal 5 tag").optional().default([]),
});

const masukanValid = {
  judul: "Belajar Node.js minggu pertama",
  isi: "Catatan tentang modul inti dan stream.",
  tag: ["node", "stream"],
};

const masukanInvalid = {
  judul: "ab",
  isi: "",
  tag: ["a", "b", "c", "d", "e", "f"],
};

console.log("--- Masukan valid ---");
const hasilValid = SkemaCatatan.safeParse(masukanValid);
console.log("sukses:", hasilValid.success);
if (hasilValid.success) {
  console.log("data:", JSON.stringify(hasilValid.data));
}

console.log("\n--- Masukan invalid ---");
const hasilInvalid = SkemaCatatan.safeParse(masukanInvalid);
console.log("sukses:", hasilInvalid.success);
if (!hasilInvalid.success) {
  for (const isu of hasilInvalid.error.issues) {
    console.log(`- ${isu.path.join(".")}: ${isu.message}`);
  }
}
bash
# pasang zod sekali untuk demo topik ini: cd /tmp/pemilihan-proyek && npm install zod@^3.25
# jalankan validasi-proyek.js dari direktori topik, NODE_PATH mengarah ke node_modules demo
NODE_PATH=/tmp/pemilihan-proyek/node_modules node validasi-proyek.js
text Keluaran
--- Masukan valid ---
sukses: true
data: {"judul":"Belajar Node.js minggu pertama","isi":"Catatan tentang modul inti dan stream.","tag":["node","stream"]}

--- Masukan invalid ---
sukses: false
- judul: Judul minimal 3 karakter
- isi: Isi tidak boleh kosong
- tag: Maksimal 5 tag

Keluaran memperlihatkan dua perilaku kunci. Pada masukan valid, safeParse mengembalikan success: true dengan data berisi nilai yang sudah lolos schema, termasuk tag yang diisi larik kosong secara default bila tidak diberikan. Pada masukan invalid, safeParse mengembalikan success: false dengan tiga isu yang masing-masing menyatakan bidang dan pesannya. Pesan tersebut ditulis dalam bahasa Indonesia agar respons 400 yang dikirim ke klien dapat dipahami tanpa penerjemahan.

Schema ini menjadi kontrak yang sama yang akan dipasang pada middleware di topik implementasi. Topik desain-api berikutnya membahas model sumber daya, endpoint, dan kontrak respons yang melengkapi schema ini, sehingga integrasi pada topik implementasi tinggal memasangkan potongan-potongan yang sudah dirancang.

Kesalahan umum#

Memilih proyek yang terlalu besar untuk portofolio. Layanan yang meniru fitur produk komersial lengkap, misalnya toko daring dengan pembayaran dan pengiriman, biasanya tidak selesai sebab lingkupnya membengkak. Pilih ranah yang dapat diselesaikan dalam dua sampai empat hari kerja dengan fokus penuh, dan tambahkan fitur hanya setelah inti berjalan.

Memulai kode tanpa daftar endpoint yang ditulis. Kebiasaan menulis rute sambil memikirkan endpoint sering kali menghasilkan URL yang tidak konsisten, misalnya POST /catatan/buat berdampingan dengan POST /catatan. Daftarkan endpoint pada tabel sederhana sebelum kode, sehingga konsistensi method dan jalur dapat diperiksa secara visual.

Mengabaikan batasan lingkungan sandbox. Proyek yang memerlukan Docker, PostgreSQL, atau layanan eksternal tidak dapat dijalankan utuh pada sandbox yang hanya menyediakan Node.js. Tetapkan dengan jelas bagian mana yang dapat dijalankan di sandbox, yaitu kode murni Node.js, dan bagian mana yang memerlukan lingkungan eksternal, yaitu kode yang memanggil basis data atau menjalankan docker compose.

Ringkasan#

  • Proyek yang dipilih adalah layanan API catatan pribadi dengan autentikasi JWT dan persistensi Prisma, yang menyentuh seluruh komponen yang sudah dibahas pada modul sebelumnya.
  • Dua sumber daya utama, yaitu pengguna dan catatan, berelasi satu-ke-banyak, dengan endpoint publik untuk registrasi dan login serta endpoint terlindungi untuk seluruh operasi catatan.
  • Struktur direktori memisahkan routes/, controllers/, services/, schemas/, middleware/, dan prisma/ agar setiap berkas memiliki satu tanggung jawab.
  • Schema zod ditulis sebagai kontrak awal sebelum kode Express, sehingga validasi-proyek.js yang dijalankan di topik ini akan dipasang sebagai middleware pada topik implementasi.

Referensi#