Server pada chapter sebelumnya sudah bisa membedakan dua jalur, tetapi rantai if (req.url === ...) akan tumbuh tak terkendali begitu endpoint kita bertambah sepuluh, apalagi lima puluh. Routing adalah kebiasaan menyusun penangan request menjadi struktur yang bisa dibaca dan dirawat: satu tempat mendaftarkan pasangan method + jalur, satu aturan untuk jalur dinamis, dan satu jawaban baku untuk permintaan yang tidak dikenal. Pada chapter ini kita bangun router sederhana memakai Map, mengurai URL dengan new URL(), dan menangani path parameter seperti /tugas/42.
Mengurai URL dengan new URL() #
req.url berupa string mentah seperti /tugas/42?urut=judul, sehingga membandingkannya dengan === mudah meleset begitu query string ikut terkirim. Solusinya adalah mengurai string tersebut menjadi object URL — class bawaan JavaScript yang juga dipakai browser:
const host = '127.0.0.1';
const url = new URL(req.url, `http://${host}`);
url.pathname; // '/tugas/42' — jalur murni tanpa query string
url.searchParams; // URLSearchParams: .get('urut') mengembalikan 'judul'
Constructor URL membutuhkan dua argumen: string yang akan diurai dan base URL, karena req.url tidak memuat nama host. Base-nya tidak harus alamat server yang sesungguhnya — nilainya hanya dipakai sebagai pelengkap sehingga hasil penguraian valid, jadi string host lokal apa pun memadai. Setelah terurai, pathname selalu berbentuk jalur bersih yang aman dibandingkan, dan searchParams menyediakan akses terurut ke setiap parameter query. Perilaku class ini terdokumentasi lengkap di MDN tentang URL
.
Tabel Rute dengan Map #
Langkah pertama router kita adalah memisahkan data rute dari logika pencocokan. Setiap rute kita simpan sebagai pasangan kunci-penangan di dalam sebuah Map, dengan kunci berbentuk string "METHOD /jalur". Penangan berupa function yang mengembalikan status dan body — bukan langsung menulis ke res — sehingga cara membalas tetap seragam untuk semua rute:
import http from 'node:http';
const host = '127.0.0.1';
const rute = new Map([
['GET /', () => ({ status: 200, body: { pesan: 'selamat datang' } })],
['GET /tugas', () => ({
status: 200,
body: { tugas: [{ id: 1, judul: 'Belajar HTTP' }, { id: 2, judul: 'Buat server' }] },
})],
['POST /tugas', () => ({ status: 201, body: { dibuat: true } })],
]);
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${host}`);
const kunci = `${req.method} ${url.pathname}`;
const balas = (status, body) => {
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(body));
};
const penangan = rute.get(kunci);
if (penangan) {
const { status, body } = penangan();
return balas(status, body);
}
const cocok = kunci.match(/^GET \/tugas\/(\d+)$/);
if (cocok) {
return balas(200, { id: Number(cocok[1]) });
}
return balas(404, { galat: `jalur ${url.pathname} tidak ditemukan` });
});
server.listen(0, '127.0.0.1', async () => {
const port = server.address().port;
const dasar = `http://127.0.0.1:${port}`;
for (const jalur of ['/', '/tugas', '/tugas/42', '/tidak-ada']) {
const r = await fetch(dasar + jalur);
console.log(r.status, jalur.padEnd(12), JSON.stringify(await r.json()));
}
const r = await fetch(dasar + '/tugas', { method: 'POST' });
console.log(r.status, '/tugas POST'.padEnd(12), JSON.stringify(await r.json()));
server.close();
});Output yang Dihasilkan:
200 / {"pesan":"selamat datang"}
200 /tugas {"tugas":[{"id":1,"judul":"Belajar HTTP"},{"id":2,"judul":"Buat server"}]}
200 /tugas/42 {"id":42}
404 /tidak-ada {"galat":"jalur /tidak-ada tidak ditemukan"}
201 /tugas POST {"dibuat":true}Mari kita telaah bagian-bagian utamanya:
- Baris 3 (
const host = '127.0.0.1'): alamat host lokal yang dipakai sebagai base URL saat menguraireq.url. - Baris 5-12 (
const rute = new Map([...])): seluruh rute statis terdaftar di satu tempat. Kunci'GET /tugas'dan'POST /tugas'sengaja memuat method — dua jalur sama dengan method berbeda adalah dua rute berbeda, sesuai semantik HTTP yang kita pelajari di Chapter Dasar Protokol HTTP. - Baris 15-16: setiap request diurai menjadi object
URL, lalu kunci pencarian dirangkai darireq.methoddanurl.pathname. Dari titik ini, query string tidak akan mengganggu pencocokan rute. - Baris 17-20 (
const balas = ...): helper kecil yang menampung cara membalas — status, header, danJSON.stringify— supaya setiap cabang cukup memanggil satu function dan tampilan response seragam. - Baris 22-26 (
rute.get(kunci)): pencocokan rute statis hanyalah pembacaanMapdengan kompleksitas konstan. Bila penangan ditemukan, hasilnya dikirim lewatbalas. - Baris 28-31 (
kunci.match(...)): rute dinamis ditangani setelah rute statis. Pola/^GET \/tugas\/(\d+)$/mencocokkanGET /tugas/diikuti angka; tanda kurung tangkap(\d+)mengambil bagian42, laluNumber(cocok[1])mengubahnya menjadi angka sebagaiid. - Baris 33: permintaan yang tidak cocok dengan apa pun dibalas
404berisi JSON — jawaban yang jauh lebih ramah bagi klien API daripada koneksi yang dibekukan atau respons kosong. - Baris 40-46: lima permintaan uji mencakup rute statis, rute dinamis, jalur yang tidak ada, dan method
POST— hasilnya terlihat berurutan di output dengan status yang berbeda-beda per baris.
Jalur Dinamis dan Urutan Pencocokan #
Urutan pengecekan pada router kita punya makna: rute statis diperiksa lebih dulu, baru pola dinamis. Dengan urutan ini, bila suatu saat kita mendaftarkan rute statis khusus GET /tugas/hari-ini, rute tersebut menang atas pola (\d+) tanpa perubahan apa pun pada kode pencocokan. Untuk pola yang lebih kompleks — misalnya /tugas/42/komentar/7 dengan dua parameter — kita cukup memperpanjang regex menjadi /^GET \/tugas\/(\d+)\/komentar\/(\d+)$/ dan membaca cocok[1] serta cocok[2].
Perlu diingat bahwa regex di atas sengaja ketat: ^ dan $ memastikan seluruh kunci cocok, dan \d+ hanya menerima digit. Permintaan seperti /tugas/abc atau /tugas/42/ tidak lolos — keduanya jatuh ke jawaban 404, yang merupakan perilaku yang tepat untuk API yang tegas.
Router kita kini bisa membaca ke mana request dituju, tetapi POST /tugas yang sesungguhnya selalu membawa data — judul tugas baru, misalnya — belum kita baca sama sekali. Body request adalah stream yang harus dikumpulkan byte demi byte; cara mengumpulkan, mengurai JSON, dan membatasi ukurannya adalah topik Chapter Membaca Request Body.