Routing adalah bagian yang paling terasa manfaatnya saat kita pindah dari module http polos ke Express. Alih-alih satu callback raksasa berisi rantai if/else, kita mendaftarkan setiap rute sebagai baris deklarasi yang mudah dipindai. Chapter ini membahas perlengkapan routing yang dipakai aplikasi nyata: route parameter, query string, urutan pencocokan, perubahan wildcard pada Express 5, router terpisah per modul, hingga penanganan 404.
Route Parameter dan Query String #
Segmen path yang diawali titik dua menjadi route parameter yang nilainya tersedia di req.params, sementara bagian setelah tanda tanya pada URL tersedia di req.query. Berkas berikut mempraktikkan keduanya sekaligus dua pola 404 yang berbeda tingkatnya.
import express from 'express';
const host = '127.0.0.1';
const app = express();
const tugas = [
{ id: 1, judul: 'Belajar Node.js', selesai: false },
{ id: 2, judul: 'Belajar Express', selesai: false },
{ id: 3, judul: 'Belajar Fastify', selesai: true },
];
// Rute paling spesifik didaftarkan lebih dulu.
app.get('/tugas/baru', (req, res) => {
res.json({ pesan: 'Formulir tugas baru' });
});
app.get('/tugas/:id', (req, res) => {
const id = Number(req.params.id);
const ditemukan = tugas.find((t) => t.id === id);
if (!ditemukan) {
res.status(404).json({ pesan: `Tugas ${id} tidak ditemukan` });
return;
}
res.json(ditemukan);
});
app.get('/tugas', (req, res) => {
const selesai = req.query.selesai;
const hasil = selesai === undefined
? tugas
: tugas.filter((t) => String(t.selesai) === selesai);
const halaman = Number(req.query.halaman ?? 1);
res.json({ filter: selesai ?? null, halaman, jumlah: hasil.length, tugas: hasil });
});
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/baru`);
console.log('rute spesifik :', r1.status, await r1.text());
const r2 = await fetch(`${base}/tugas/2`);
console.log('rute parameter:', r2.status, await r2.text());
const r3 = await fetch(`${base}/tugas/9`);
console.log('parameter 404 :', r3.status, await r3.text());
const r4 = await fetch(`${base}/tugas?selesai=true&halaman=2`);
console.log('query filter :', r4.status, await r4.text());
const r5 = await fetch(`${base}/tak-kenal`);
console.log('fallback 404 :', r5.status, await r5.text());
server.close();
server.closeAllConnections();
});Output yang Dihasilkan:
rute spesifik : 200 {"pesan":"Formulir tugas baru"}
rute parameter: 200 {"id":2,"judul":"Belajar Express","selesai":false}
parameter 404 : 404 {"pesan":"Tugas 9 tidak ditemukan"}
query filter : 200 {"filter":"true","halaman":2,"jumlah":1,"tugas":[{"id":3,"judul":"Belajar Fastify","selesai":true}]}
fallback 404 : 404 {"pesan":"Rute tidak ditemukan"}Mari kita bedah bagian-bagian kuncinya:
- Baris 17-19 (
app.get('/tugas/:id')): segmen:idmenangkap satu segmen path. Nilainya selalu string, sehinggaNumber(req.params.id)diperlukan sebelum perbandingan dengan angka di data. - Baris 20-23 (404 di dalam rute): rute
/tugas/9cocok polanya, tetapi datanya tidak ada. Karena itu handler sendiri yang mengirim 404 — berbeda kasusnya dengan path yang tidak cocok rute mana pun. - Baris 28 (
req.query.selesai): nilai query juga selalu string, terlihat dari respons"filter":"true". Perhatikan pula baris 32:Number(req.query.halaman ?? 1)mengubah"2"menjadi2. - Baris 36 (
app.usetanpa path): penampung terakhir untuk semua permintaan tanpa rute yang cocok — fallback 404 di tingkat aplikasi.
Pola Path yang Sering Dipakai #
| Pola | Contoh kecocokan | Isi req.params |
|---|---|---|
/tugas | /tugas | kosong |
/tugas/:id | /tugas/7 | { id: '7' } |
/tugas/:id/komentar/:komentarId | /tugas/7/komentar/3 | { id: '7', komentarId: '3' } |
/arsip/*splat | /arsip/2026/laporan.pdf | { splat: ['2026', 'laporan.pdf'] } |
Satu pola di tabel terakhir masih asing: *splat. Pola wildcard inilah yang berubah besar di Express 5.
Wildcard Berubah pada Express 5 #
Pada Express 4, pola /berkas/* sah dipakai untuk menangkap sisa path. Express 5 mengganti pustaka pencocokan path, dan wildcard tanpa nama sekarang ditolak keras saat aplikasi dimulai:
import express from 'express';
try {
express().get('/berkas/*', (req, res) => res.send('rute lama'));
} catch (err) {
console.log(err.message.split(';')[0]);
}Output yang Dihasilkan:
Missing parameter name at index 9: /berkas/*Bentuk yang benar adalah memberi nama pada wildcard — misalnya *splat — lalu nilai tangkapannya diambil dari req.params.splat:
import express from 'express';
const host = '127.0.0.1';
const app = express();
app.get('/arsip/*splat', (req, res) => {
res.json({ jalur: req.params.splat.join('/') });
});
const server = app.listen(0, host, async () => {
const base = `http://${host}:${server.address().port}`;
const r = await fetch(`${base}/arsip/2026/laporan.pdf`);
console.log(r.status, await r.text());
server.close();
server.closeAllConnections();
});Output yang Dihasilkan:
200 {"jalur":"2026/laporan.pdf"}Berbeda dengan parameter biasa, hasil tangkapan wildcard berupa array berisi segmen-segmen path, sehingga join('/') menyatukannya kembali menjadi 2026/laporan.pdf.
Router Terpisah dan Route Chaining #
Ketika aplikasi membesar, seluruh rute dalam satu berkas menjadi sulit dirawat. Express menyediakan express.Router() — mini-aplikasi yang punya rute dan middleware sendiri, lalu dipasang pada aplikasi utama dengan sebuah prefix. Untuk rute satu path dengan banyak method, method app.route() memungkinkan deklarasi berantai.
import express from 'express';
const host = '127.0.0.1';
const app = express();
app.use(express.json());
// Router modul "catatan" beserta middleware miliknya.
const router = express.Router();
router.use((req, res, next) => {
console.log(`[catatan] ${req.method} ${req.url}`);
next();
});
const catatan = [];
router.get('/', (req, res) => res.json(catatan));
router.post('/', (req, res) => {
catatan.push({ isi: req.body.isi });
res.status(201).json(catatan.at(-1));
});
router.get('/:kode', (req, res) => {
res.json({ kode: req.params.kode });
});
app.use('/api/catatan', router);
// app.route(): satu path untuk banyak method.
app.route('/ping')
.get((req, res) => res.json({ pesan: 'pong (GET)' }))
.post((req, res) => res.json({ pesan: 'pong (POST)' }));
const server = app.listen(0, host, async () => {
const base = `http://${host}:${server.address().port}`;
const r1 = await fetch(`${base}/api/catatan`);
console.log('router GET :', r1.status, await r1.text());
const r2 = await fetch(`${base}/api/catatan`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ isi: 'Belajar Router' }),
});
console.log('router POST :', r2.status, await r2.text());
const r3 = await fetch(`${base}/api/catatan/pertama`);
console.log('router param :', r3.status, await r3.text());
const r4 = await fetch(`${base}/ping`);
console.log('ping GET :', r4.status, await r4.text());
const r5 = await fetch(`${base}/ping`, { method: 'POST' });
console.log('ping POST :', r5.status, await r5.text());
server.close();
server.closeAllConnections();
});Output yang Dihasilkan:
[catatan] GET /
router GET : 200 []
[catatan] POST /
router POST : 201 {"isi":"Belajar Router"}
[catatan] GET /pertama
router param : 200 {"kode":"pertama"}
ping GET : 200 {"pesan":"pong (GET)"}
ping POST : 200 {"pesan":"pong (POST)"}Perhatikan tiga hal pada berkas di atas:
- Baris 10-13 (
router.use): middleware ini hanya berlaku untuk permintaan yang masuk lewat router. Di dalam router,req.urlsudah dipangkas prefix — tampil/dan/pertama, bukan/api/catatan/.... - Baris 17, 19, dan 24: path di dalam router ditulis relatif —
/berarti/api/catatan, dan/:kodeberarti/api/catatan/pertama. - Baris 31-33 (
app.route('/ping')): satu path didaftarkan sekali, lalu.get()dan.post()dirantai. Pola ini menghindari path yang sama tertulis dua kali.
Router menjawab kebutuhan struktur; wildcard dan parameter menjawab kebutuhan pencocokan. Satu pertanyaan penting masih tersisa: bagaimana satu potongan kode bisa berjalan untuk banyak rute sekaligus — misalnya mencatat log atau memeriksa token? Jawabannya adalah middleware, topik Chapter Middleware pada Express.