Pada Section REST API sebelumnya, kita membangun API lengkap hanya dengan module http bawaan Node.js. Pendekatan tersebut cukup untuk aplikasi kecil karena tidak ada dependency tambahan dan seluruh perilaku server ada di depan mata kita. Namun seiring daftar endpoint bertambah, kita mulai menulis ulang kode yang sama berulang-ulang: pembandingan method dan pathname, pengumpulan potongan body, pengubahan JSON, serta penanganan error yang tersebar di banyak cabang if. Chapter ini membantu kita menimbang kapan framework web seperti Express atau Fastify mulai memberi manfaat nyata, dan kapan module http polos justru pilihan yang lebih tepat.
Bekal dari Module http #
Module http memberi kita fondasi yang lengkap namun mentah: objek req adalah Readable Stream, objek res adalah Writable Stream, dan tidak ada konsep “rute” sama sekali. Semua keputusan routing harus kita tulis sendiri di dalam satu callback createServer. Berkas berikut mengimplementasikan mini API daftar tugas dengan dua endpoint dan satu fallback 404, sepenuhnya tanpa framework.
import http from 'node:http';
const host = '127.0.0.1';
const tugas = [
{ id: 1, judul: 'Belajar Node.js' },
{ id: 2, judul: 'Belajar Express' },
];
function kirimJson(res, status, data) {
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(data));
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${host}`);
if (req.method === 'GET' && url.pathname === '/tugas') {
kirimJson(res, 200, tugas);
} else if (req.method === 'POST' && url.pathname === '/tugas') {
const potongan = [];
for await (const byte of req) potongan.push(byte);
const data = JSON.parse(Buffer.concat(potongan).toString('utf8'));
tugas.push({ id: tugas.length + 1, judul: data.judul });
kirimJson(res, 201, tugas);
} else {
kirimJson(res, 404, { pesan: 'Rute tidak ditemukan' });
}
});
server.listen(0, host, async () => {
const base = `http://${host}:${server.address().port}`;
const r1 = await fetch(`${base}/tugas`);
console.log('GET /tugas :', r1.status, await r1.text());
const r2 = await fetch(`${base}/tugas`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ judul: 'Belajar Fastify' }),
});
console.log('POST /tugas:', r2.status, await r2.text());
const r3 = await fetch(`${base}/lain`);
console.log('GET /lain :', r3.status, await r3.text());
server.close();
server.closeAllConnections();
});Output yang Dihasilkan:
GET /tugas : 200 [{"id":1,"judul":"Belajar Node.js"},{"id":2,"judul":"Belajar Express"}]
POST /tugas: 201 [{"id":1,"judul":"Belajar Node.js"},{"id":2,"judul":"Belajar Express"},{"id":3,"judul":"Belajar Fastify"}]
GET /lain : 404 {"pesan":"Rute tidak ditemukan"}Mari kita amati bagian server pada baris-baris kode di atas:
- Baris 8-11 (
function kirimJson): helper buatan sendiri untuk mengisi status code, headercontent-type, dan body JSON. Pada framework, pekerjaan ini sudah tersedia sebagai method bawaan. - Baris 15 (
new URL(req.url, ...)): setiap permintaan harus diurai secara manual untuk mendapatkanpathnamedan query string. - Baris 17-19 (rantai
if/else if): routing diimplementasikan sebagai pembandingan string. Dua kriteria — method dan path — harus diperiksa bersamaan di setiap cabang. - Baris 20-22 (
for await+Buffer.concat): body permintaan POST berupa stream, sehingga potongan-potongan byte harus dikumpulkan dulu, digabung, lalu di-parse menjadi JSON. - Baris 25-27 (
else): cabang terakhir menangani semua kombinasi method dan path yang tidak dikenali — inilah 404 yang kita rancang sendiri.
Dua endpoint saja sudah menghasilkan 28 baris kode server. Bayangkan jika endpoint bertambah menjadi 30 dengan berbagai parameter, validasi, dan format respons.
Pekerjaan yang Tidak Disediakan Module http #
| Kebutuhan aplikasi nyata | Kondisi dengan module http polos |
|---|---|
Routing berparameter seperti /tugas/:id | Pembandingan string atau regular expression manual di setiap cabang |
| Pembacaan body JSON | Kumpulkan chunk, gabungkan Buffer, panggil JSON.parse, bungkus try/catch |
| Middleware seperti logging dan autentikasi | Tidak ada mekanisme bawaan; logika lintas rute disalin atau digabung manual |
| Validasi input | Pengecekan tipe dan ukuran field ditulis manual di setiap handler |
| Penanganan error terpusat | try/catch di setiap cabang; format error mudah tidak konsisten |
| Penyajian berkas statis | Baca berkas manual dan atur header content-type sesuai ekstensi |
Tabel di atas bukan berarti module http kurang mampu — semua baris di kolom kanan tetap bisa ditulis dan berjalan cepat. Masalahnya muncul pada skala: kode yang sama ditulis berulang di banyak cabang, dan setiap penulis cabang bisa membuat keputusan sedikit berbeda sehingga konsistensi API menurun.
Versi yang Sama dengan Express #
Framework web mengambil alih pekerjaan repetitif tersebut dan menukarnya dengan deklarasi singkat. Bandingkan berkas berikut dengan server-polos.mjs — ketiga permintaan uji menghasilkan status dan body yang persis sama.
import express from 'express';
const host = '127.0.0.1';
const app = express();
app.use(express.json());
const tugas = [
{ id: 1, judul: 'Belajar Node.js' },
{ id: 2, judul: 'Belajar Express' },
];
app.get('/tugas', (req, res) => res.json(tugas));
app.post('/tugas', (req, res) => {
tugas.push({ id: tugas.length + 1, judul: req.body.judul });
res.status(201).json(tugas);
});
app.use((req, res) => res.status(404).json({ pesan: 'Rute tidak ditemukan' }));
const server = app.listen(0, host, async () => {
const base = `http://${host}:${server.address().port}`;
const r1 = await fetch(`${base}/tugas`);
console.log('GET /tugas :', r1.status, await r1.text());
const r2 = await fetch(`${base}/tugas`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ judul: 'Belajar Fastify' }),
});
console.log('POST /tugas:', r2.status, await r2.text());
const r3 = await fetch(`${base}/lain`);
console.log('GET /lain :', r3.status, await r3.text());
server.close();
server.closeAllConnections();
});Output yang Dihasilkan:
GET /tugas : 200 [{"id":1,"judul":"Belajar Node.js"},{"id":2,"judul":"Belajar Express"}]
POST /tugas: 201 [{"id":1,"judul":"Belajar Node.js"},{"id":2,"judul":"Belajar Express"},{"id":3,"judul":"Belajar Fastify"}]
GET /lain : 404 {"pesan":"Rute tidak ditemukan"}Perbedaan pentingnya ada pada 19 baris pertama:
- Baris 5 (
app.use(express.json())): satu baris menggantikan seluruh rutinitas pengumpulan chunk danJSON.parse; hasilnya tersedia sebagaireq.body. - Baris 12 dan 14 (
app.get/app.post): method dan path digabung menjadi satu deklarasi rute, sehingga rantaiif/elsehilang. - Baris 15 (
req.body.judul): body sudah berupa object JavaScript, siap dipakai tanpaBuffer.concat. - Baris 16 (
res.status(201).json(...)): status, header, dan serialization JSON ditangani method bawaan yang bisa dirantai. - Baris 19 (
app.usetanpa path): middleware tanpa path menjadi penampung semua permintaan yang tidak cocok dengan rute mana pun — fallback 404 dalam satu baris.
Menimbang Keputusan #
Framework memberi manfaat ketika sebagian besar sinyal berikut mulai muncul pada proyek kita:
- Jumlah endpoint bertambah hingga routing manual menjadi rantai
if/elsepanjang yang sulit dipindai. - Kebutuhan lintas rute muncul, misalnya pencatatan log untuk setiap permintaan atau pemeriksaan token sebelum handler dijalankan.
- Format respons dan error perlu konsisten di seluruh API agar mudah dikonsumsi klien.
- Anggota tim lebih dari satu orang, sehingga konvensi framework membantu menyamakan gaya kode.
Sebaliknya, framework membawa biaya yang perlu kita sadari: daftar dependencies bertambah dan harus ikut diperbarui saat ada rilis keamanan, ada lapisan abstraksi yang menjauhkan kita dari perilaku mentah HTTP, serta waktu belajar awal untuk anggota tim yang belum familiar. Untuk server kecil — webhook tunggal, health check, atau alat internal satu endpoint — module http polos tetap pilihan yang jujur dan ringan.
| Pilihan | Kekuatan utama | Biaya utama |
|---|---|---|
Module http polos | Tanpa dependency, kendali penuh, cocok untuk server kecil | Semua pekerjaan repetitif ditulis sendiri |
| Express | Ekosistem terbesar, dokumentasi melimpah, API sederhana | Performa menengah dibanding framework baru |
| Fastify | Skema JSON untuk validasi dan serialization cepat, performa tinggi | Ekosistem lebih muda, konsep plugin perlu dipelajari |
Kita akan membedah Express dan Fastify satu per satu pada chapter-chapter berikutnya, lalu menutup dengan panduan memilih. Jika kita memutuskan untuk memakai framework, langkah pertama adalah memahami bentuk paling sederhana dari aplikasi Express — itulah materi Chapter Express: Dasar.