Aplikasi Node.js sering berhadapan dengan alamat web: endpoint API milik layanan lain, tautan yang dikirim pengguna, sampai lokasi berkas dalam format file://. Memecah alamat semacam itu dengan split("?") atau regular expression memang bisa, tetapi aturan URL ternyata penuh detail — port, query string berulang, karakter yang perlu di-escape — dan satu saja yang terlewat, hasilnya salah. Module node:url menyediakan penguraian dan penyusunan URL yang mengikuti standar web, sehingga kode kita memakai parser yang sama dengan browser.

Mengurai URL dengan Class URL #

Class URL bisa diimpor dari node:url, dan karena URL juga merupakan global Node.js, class tersebut bahkan bisa dipakai tanpa import — sama seperti di browser. Constructor new URL() menerima satu string alamat, mengurainya, lalu menyimpan tiap bagian pada property yang mudah dibaca. Berkas urai-url.mjs membedah satu alamat halaman dokumentasi yang punya query string dan bagian hash:

js
import { URL } from "node:url";

const alamat = new URL(
  "https://nodejs.org/api/url.html?format=markdown&tag=baru#bagian",
);

console.log("protocol:", alamat.protocol);
console.log("host    :", alamat.host);
console.log("pathname:", alamat.pathname);
console.log("search  :", alamat.search);
console.log("hash    :", alamat.hash);
console.log("format  :", alamat.searchParams.get("format"));

try {
  new URL("bukan-url");
} catch (error) {
  console.log("error   :", error.name, "-", error.message);
}

Output yang Dihasilkan:

text
protocol: https:
host    : nodejs.org
pathname: /api/url.html
search  : ?format=markdown&tag=baru
hash    : #bagian
format  : markdown
error   : TypeError - Invalid URL

Mari kita telaah baris-baris kode di atas:

  1. Baris 3-5 (new URL(...)): Satu string alamat diurai menjadi object. Property protocol, host, pathname, search, dan hash menyimpan bagian-bagiannya — search masih mentah beserta tanda ?, hash beserta tanda #.
  2. Baris 12 (alamat.searchParams.get("format")): Setiap instance URL otomatis membawa property searchParams berupa object URLSearchParams, sehingga nilai query string bisa langsung dibaca tanpa parsing tambahan.
  3. Baris 14-18 (try/catch): Constructor URL memvalidasi masukan — string yang bukan URL valid membuat TypeError terlempar, bukan object rusak yang lolos diam-diam. Validasi sejak pembuatan seperti ini membuat error terdeteksi dini, tepat di titik data masuk.

Karena URL adalah global, snippet pendek berikut berjalan tanpa satu baris import pun:

js
const alamat = new URL("https://nodejs.org/en/learn/#daftar");
console.log(typeof URL);
console.log(alamat.hostname);

Output yang Dihasilkan:

text
function
nodejs.org

Perlu dicatat perbedaan dua property yang mirip: host menyimpan nama domain beserta port bila ada (example.com:8080), sedangkan hostname hanya nama domainnya. Dua-duanya berguna — pilih sesuai kebutuhan.

Query String dengan URLSearchParams #

Query string adalah bagian URL setelah tanda ?, dan strukturnya adalah daftar pasangan kunci-nilai — satu kunci boleh muncul lebih dari sekali. Class URLSearchParams mengelola daftar tersebut: bisa dibuat dari string query, dari property searchParams milik URL, lalu dimodifikasi dan dirangkai kembali. Berkas query-params.mjs memperlihatkan operasi yang paling sering dipakai:

js
import { URLSearchParams } from "node:url";

const params = new URLSearchParams("status=selesai&tag=pekerjaan&tag=penting");

console.log("get     :", params.get("status"));
console.log("getAll  :", params.getAll("tag"));
console.log("ada?    :", params.has("halaman"));

params.set("status", "tertunda");
params.append("tag", "mendesak");

for (const [kunci, nilai] of params) {
  console.log("pasangan:", kunci, "=", nilai);
}

console.log("toString:", params.toString());

Output yang Dihasilkan:

text
get     : selesai
getAll  : [ 'pekerjaan', 'penting' ]
ada?    : false
pasangan: status = tertunda
pasangan: tag = pekerjaan
pasangan: tag = penting
pasangan: tag = mendesak
toString: status=tertunda&tag=pekerjaan&tag=penting&tag=mendesak

Mari kita telaah baris-baris kode di atas:

  1. Baris 3: Object dibuat langsung dari string query. Kunci tag sengaja muncul dua kali untuk memperlihatkan perilaku kunci berulang.
  2. Baris 5-7: get() mengambil nilai pertama, getAll() mengambil seluruh nilai dalam bentuk array, dan has() memeriksa keberadaan kunci tanpa mengambil nilainya.
  3. Baris 9-10: set() mengganti nilai kunci yang sudah ada di posisinya, sedangkan append() menambahkan pasangan baru di akhir — dua operasi dengan hasil akhir yang berbeda.
  4. Baris 12-14: Object ini bisa di-iterasi dengan for...of karena merupakan iterable; tiap iterasi menghasilkan array [kunci, nilai], dan urutannya mengikuti urutan penyimpanan.
  5. Baris 16 (params.toString()): Seluruh pasangan dirangkai kembali menjadi string query yang siap ditempelkan ke URL, lengkap dengan penanganan karakter khusus.

Menyusun Kembali URL dengan format() #

Penguraian punya kebalikannya: menyusun URL dari bagian-bagian. Function format() menerima object berisi property protocol, host, pathname, dan search, lalu merangkainya menjadi satu alamat utuh:

js
import { format } from "node:url";

const bagian = {
  protocol: "https:",
  host: "nodejs.org",
  pathname: "/api/url.html",
  search: "?format=markdown",
};

console.log(format(bagian));

Output yang Dihasilkan:

text
https://nodejs.org/api/url.html?format=markdown

Cara lain yang sama validnya adalah membuat new URL() lalu mengubah property-nya satu per satu; property href selalu berisi alamat hasil perubahan terbaru. format() unggul saat bagian-bagian datang dari data terpisah — misalnya konfigurasi host dan path dari environment variable — karena seluruh perakitan terjadi dalam satu panggilan function.

import.meta.url dan fileURLToPath() #

Ada satu pola khusus di ESM yang memakai module ini dan akan sering kita jumpai: import.meta.url. Pada setiap module ES, ekspresi tersebut berisi alamat file:// dari berkas module itu sendiri — semacam “alamat diri sendiri”. Dengan menjadikannya basis, kita bisa menghitung alamat berkas lain yang berada di sebelah module:

js
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import path from "node:path";

const urlTugas = new URL("./tugas.json", import.meta.url);
console.log("protokol:", urlTugas.protocol);

const lokasiTugas = fileURLToPath(urlTugas);
console.log("berkas  :", path.basename(lokasiTugas));

const isi = await readFile(lokasiTugas, "utf8");
const tugas = JSON.parse(isi);
console.log("pelaku  :", tugas.pelaku);
console.log("tugas   :", tugas.kegiatan);

Output yang Dihasilkan:

text
protokol: file:
berkas  : tugas.json
pelaku  : Siti
tugas   : Menyusun laporan bulanan

Mari kita telaah baris-baris kode di atas:

  1. Baris 5 (new URL("./tugas.json", import.meta.url)): Argumen kedua new URL() adalah basis resolusi — ./tugas.json dihitung relatif terhadap lokasi berkas berkas-lokal.mjs, bukan direktori kerja tempat perintah node dijalankan. Pola ini adalah padanan modern __dirname di CommonJS.
  2. Baris 8 (fileURLToPath(urlTugas)): Function ini mengubah URL file:// menjadi path berkas biasa yang dimengerti function lain seperti readFile(). Hasilnya berupa path absolut menuju folder chapter ini — nilainya berbeda di setiap komputer, sehingga contoh sengaja hanya mencetak basename()-nya.
  3. Baris 11-14: Berkas tugas.json dibaca lalu diurai dengan JSON.parse(). Module node:fs akan kita bedah tuntas pada Section Filesystem; untuk sekarang cukup ketahui bahwa readFile() mengembalikan isi berkas sebagai string.

Dengan node:path untuk berkas dan node:url untuk alamat, dua format “lokasi” utama sudah kita kuasai. Chapter berikutnya berpindah ke module perkakas serbaguna node:util — tempat kami menemukan utilitas untuk memproses argumen CLI, membungkus callback menjadi promise, hingga merapikan output debugging.

Referensi lanjutan untuk topik chapter ini: