Kenapa ini penting
Bayangkan kamu kerja di bagian pendaftaran sebuah aplikasi. Setiap hari masuk ribuan data: email, nomor HP, password, kode pos, tanggal lahir. Sebagian diketik asal, misalnya budi@gmail tanpa .com, nomor HP 0812-abc, atau password 123. Kalau data rusak lolos ke database, akibatnya baru terasa belakangan: email konfirmasi tidak terkirim, OTP nyasar, akun gampang dibobol.
Kamu bisa saja memeriksa semuanya dengan if dan indexOf berlapis, tapi kodenya cepat jadi panjang dan rapuh. Di sinilah Regular Expression (disingkat regex atau RegExp) berguna. Regex adalah bahasa mini untuk mendeskripsikan pola teks. Satu baris regex bisa menggantikan puluhan baris if. Regex dipakai di mana-mana: validasi form, mencari dan mengganti teks di editor kode (VS Code punya tombol .* untuk mode regex), membersihkan data CSV, sampai memecah log server.
Analogi sehari-hari: regex itu seperti cetakan kue. Kamu tidak mendeskripsikan satu kue tertentu, tapi bentuknya. Adonan apa pun yang pas di cetakan dianggap "cocok". Pola \d{4} artinya "empat digit berturut-turut", jadi 2026, 0812, dan 9999 semuanya pas di cetakan itu.
Satu peringatan sejak awal: regex mudah ditulis tapi sulit dibaca. Kuncinya adalah mulai dari pola kecil, uji satu per satu, lalu gabungkan. Situs seperti regex101.com (pilih flavor ECMAScript/JavaScript) sangat membantu karena menjelaskan setiap bagian pola dan menyorot hasil cocoknya secara langsung.
Konsep inti
Dua cara membuat regex: literal dan RegExp
Di JavaScript, regex adalah object. Ada dua cara membuatnya:
- Literal: ditulis di antara dua garis miring, misalnya
/kucing/i. Cara ini paling sering dipakai karena ringkas, dan pola diproses sekali saat kode dimuat. - Constructor
RegExp:new RegExp("kucing", "i"). Polanya berupa string, jadi kamu bisa menyusunnya secara dinamis, misalnya dari kata kunci yang diketik pengguna.
Ada jebakan di constructor: karena polanya string, backslash harus ditulis dua kali. Literal /\d+/ sama dengan new RegExp("\\d+"). Kalau kamu menulis new RegExp("\d+"), JavaScript membaca "\d" sebagai huruf d biasa, dan polamu diam-diam salah.
Flag: pengubah perilaku
Flag ditulis setelah garis miring penutup. Yang paling sering kamu pakai:
| Flag | Nama | Arti |
|---|---|---|
g |
global | Cari semua kecocokan, bukan hanya yang pertama |
i |
ignore case | Huruf besar dan kecil dianggap sama |
m |
multiline | ^ dan $ berlaku per baris, bukan per seluruh teks |
u |
unicode | Memproses teks sebagai karakter Unicode utuh (emoji, aksara non-Latin) dan menyalakan \p{...} |
Flag bisa digabung, misalnya /halo/gi. Flag u sering dilupakan, padahal penting kalau teksmu mengandung emoji. Tanpa u, emoji seperti 😀 dihitung sebagai dua "potongan" (surrogate pair), sehingga titik . hanya menangkap separuhnya.
Character class: "satu karakter dari kelompok ini"
Character class mewakili satu karakter yang memenuhi syarat tertentu.
| Pola | Cocok dengan |
|---|---|
[abc] |
salah satu dari a, b, atau c |
[a-z] |
satu huruf kecil a sampai z |
[A-Za-z0-9] |
satu huruf atau angka |
[^0-9] |
satu karakter yang bukan angka (tanda ^ di dalam kurung siku artinya negasi) |
\d |
satu digit, sama dengan [0-9] |
\w |
satu "karakter kata": huruf Latin, angka, atau underscore |
\s |
satu whitespace: spasi, tab, baris baru |
\D, \W, \S |
kebalikan dari \d, \w, \s |
. |
karakter apa saja kecuali baris baru |
Perhatikan bahwa \w tidak mengenali huruf seperti é atau aksara non-Latin. Kalau butuh "huruf dari bahasa apa pun", pakai \p{L} dengan flag u.
Anchor: posisi, bukan karakter
^ artinya awal teks, $ artinya akhir teks. Anchor sangat penting untuk validasi. Pola /\d{4}/ akan cocok dengan "abc1234xyz" karena di dalamnya ada empat digit. Kalau maksudmu "seluruh isi harus empat digit", tulis /^\d{4}$/. Ada juga \b (word boundary), batas antara karakter kata dan bukan-kata, berguna untuk mencari kata utuh.
Quantifier: berapa kali
Quantifier ditempel setelah sesuatu untuk menentukan jumlah pengulangannya.
| Quantifier | Arti |
|---|---|
* |
0 kali atau lebih |
+ |
1 kali atau lebih |
? |
0 atau 1 kali (opsional) |
{3} |
tepat 3 kali |
{2,5} |
2 sampai 5 kali |
{8,} |
minimal 8 kali |
Secara default quantifier bersifat greedy (rakus): ia mengambil sebanyak mungkin. Pada teks <b>tebal</b>, pola /<.+>/ akan menangkap seluruh string, bukan hanya <b>. Tambahkan ? setelah quantifier agar jadi lazy (secukupnya): /<.+?>/ hanya menangkap <b>.
Group dan named group
Tanda kurung ( ) punya dua fungsi. Pertama, mengelompokkan: (ha)+ cocok dengan ha, haha, hahaha. Kedua, menangkap (capture): bagian yang cocok di dalam kurung disimpan dan bisa kamu ambil nanti.
Kalau hanya butuh pengelompokan tanpa menyimpan, pakai non-capturing group (?: ). Named group (?<nama>...) memberi nama pada tangkapan, jadi kamu tidak perlu mengingat "ini grup nomor berapa". Hasilnya bisa diakses lewat match.groups.nama, dan kode jadi jauh lebih mudah dibaca.
Ada juga lookahead (?=...): "pastikan di depan ada ini, tapi jangan ikut ditangkap". Lookahead adalah trik standar untuk validasi password, misalnya "harus ada minimal satu angka di mana pun posisinya".
Tanda | artinya "atau": /kucing|anjing/ cocok dengan salah satunya.
Method yang perlu kamu kuasai
| Method | Dipanggil pada | Mengembalikan |
|---|---|---|
regex.test(str) |
regex | true / false, cocok untuk validasi |
str.match(regex) |
string | Tanpa g: detail kecocokan pertama (termasuk grup). Dengan g: array semua teks yang cocok. Tidak ada yang cocok: null |
str.matchAll(regex) |
string | Iterator semua kecocokan lengkap dengan grup. Regex wajib memakai g |
str.replace(regex, pengganti) |
string | String baru. Dengan g semua diganti, tanpa g hanya yang pertama |
str.split(regex) |
string | Array potongan teks |
Di dalam string pengganti replace, $1 merujuk grup pertama dan $<nama> merujuk named group. Pengganti juga bisa berupa function, yang dipanggil untuk setiap kecocokan.
Contoh kode
Dasar: membuat regex dan flag
// Literal vs constructor
const pola1 = /kopi/i;
const kataKunci = "kopi";
const pola2 = new RegExp(kataKunci, "i"); // dinamis, dari variabel
console.log(pola1.test("Saya suka KOPI susu")); // → true
console.log(pola2.test("Teh manis")); // → false
// Backslash di constructor harus dobel
const angkaA = /\d+/;
const angkaB = new RegExp("\\d+");
console.log(angkaA.test("abc123"), angkaB.test("abc123")); // → true true
// Flag g: semua kecocokan
const kalimat = "Kopi pagi, kopi siang, KOPI malam";
console.log(kalimat.match(/kopi/gi)); // → [ 'Kopi', 'kopi', 'KOPI' ]
console.log(kalimat.match(/kopi/)[0], kalimat.match(/kopi/).index); // → 'kopi' 11 (hanya yang pertama cocok persis)
// Flag m: ^ berlaku di setiap baris
const daftar = "apel\nbayam\nalpukat";
console.log(daftar.match(/^a\w+/gm)); // → [ 'apel', 'alpukat' ]
console.log(daftar.match(/^a\w+/g)); // → [ 'apel' ] (tanpa m, ^ hanya awal teks)
// Flag u: emoji dihitung sebagai satu karakter
console.log(/^.$/.test("😀")); // → false (tanpa u, emoji = 2 potongan)
console.log(/^.$/u.test("😀")); // → true
Character class, quantifier, greedy vs lazy
const teks = "Order #A12 dikirim ke kode pos 40115 pada 2026-10-23";
console.log(teks.match(/\d{5}/)[0]); // → '40115'
console.log(teks.match(/#[A-Z]\d+/)[0]); // → '#A12'
console.log(teks.match(/\d+/g)); // → [ '12', '40115', '2026', '10', '23' ]
// Anchor: validasi seluruh isi
console.log(/\d{4}/.test("abc1234xyz")); // → true (ada 4 digit di dalamnya)
console.log(/^\d{4}$/.test("abc1234xyz")); // → false (isinya bukan cuma 4 digit)
console.log(/^\d{4}$/.test("1234")); // → true
// Greedy vs lazy
const html = "<b>tebal</b> dan <i>miring</i>";
console.log(html.match(/<.+>/)[0]); // → '<b>tebal</b> dan <i>miring</i>' (rakus)
console.log(html.match(/<.+?>/g)); // → [ '<b>', '</b>', '<i>', '</i>' ] (secukupnya)
Group, named group, dan matchAll
const tanggal = "Rilis: 2026-10-23";
// Capture group biasa: diakses lewat index
const m1 = tanggal.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(m1[0], m1[1], m1[2], m1[3]); // → '2026-10-23' '2026' '10' '23'
// Named group: lebih jelas
const m2 = tanggal.match(/(?<tahun>\d{4})-(?<bulan>\d{2})-(?<hari>\d{2})/);
console.log(m2.groups.hari, m2.groups.bulan, m2.groups.tahun); // → '23' '10' '2026'
// matchAll: semua kecocokan + grupnya. Wajib flag g.
const harga = "Kopi Rp18000, Roti Rp12500, Air Rp5000";
const polaHarga = /(?<nama>\w+) Rp(?<nominal>\d+)/g;
for (const m of harga.matchAll(polaHarga)) {
console.log(`${m.groups.nama}: ${Number(m.groups.nominal)}`);
}
// → Kopi: 18000
// → Roti: 12500
// → Air: 5000
// Mengubah jadi array object, lalu dihitung totalnya
const items = [...harga.matchAll(polaHarga)].map((m) => ({
nama: m.groups.nama,
nominal: Number(m.groups.nominal),
}));
const total = items.reduce((jml, it) => jml + it.nominal, 0);
console.log(total); // → 35500
replace: membersihkan dan memformat teks
// Ganti format tanggal YYYY-MM-DD menjadi DD/MM/YYYY pakai named group
const iso = "2026-10-23";
console.log(iso.replace(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, "$<d>/$<m>/$<y>")); // → '23/10/2026'
// Rapikan spasi berlebih
const berantakan = " Halo dunia JS ";
console.log(berantakan.trim().replace(/\s+/g, " ")); // → 'Halo dunia JS'
// Ambil digit saja dari nomor HP yang diketik dengan spasi/strip
console.log("0812-3456 7890".replace(/\D/g, "")); // → '081234567890'
// Pengganti berupa function: format angka jadi Rupiah
const struk = "Total: 1500000 dan diskon 25000";
const rapi = struk.replace(/\d+/g, (angka) => "Rp" + Number(angka).toLocaleString("id-ID"));
console.log(rapi); // → 'Total: Rp1.500.000 dan diskon Rp25.000'
// Membuat slug URL dari judul artikel
function slugify(judul) {
return judul
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s-]/g, "") // buang tanda baca
.replace(/\s+/g, "-") // spasi jadi strip
.replace(/-+/g, "-"); // strip dobel jadi satu
}
console.log(slugify("Belajar JS: DOM & Event, Gampang!")); // → 'belajar-js-dom-event-gampang'
// Sensor kata kasar (kata utuh, tidak peduli huruf besar/kecil)
const kasar = /\b(bodoh|goblok)\b/gi;
console.log("Dasar Bodoh!".replace(kasar, (k) => "*".repeat(k.length))); // → 'Dasar *****!'
Validasi dunia nyata: email, nomor HP Indonesia, password
// 1. Email: cek bentuk umum "sesuatu@domain.tld".
// Validasi email 100% sempurna dengan regex itu mustahil; cara pasti adalah kirim email verifikasi.
const polaEmail = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
console.log(polaEmail.test("budi@gmail.com")); // → true
console.log(polaEmail.test("budi.s@kantor.co.id")); // → true
console.log(polaEmail.test("budi@gmail")); // → false (tidak ada .tld)
console.log(polaEmail.test("budi @gmail.com")); // → false (ada spasi)
// 2. Nomor HP Indonesia: diawali 08 / 628 / +628, total 10-13 digit kalau pakai awalan 0.
const polaHP = /^(?:\+62|62|0)8[1-9]\d{7,10}$/;
function cekHP(input) {
const bersih = input.replace(/[\s-]/g, ""); // toleransi spasi & strip
return polaHP.test(bersih);
}
console.log(cekHP("081234567890")); // → true
console.log(cekHP("+62 812-3456-7890")); // → true
console.log(cekHP("628123456789")); // → true
console.log(cekHP("0212345678")); // → false (nomor rumah Jakarta, bukan HP)
console.log(cekHP("08123")); // → false (terlalu pendek)
// Normalisasi ke format 62xxx sebelum disimpan atau dikirim ke API WhatsApp
function normalisasiHP(input) {
return input.replace(/[\s-]/g, "").replace(/^(?:\+62|0)/, "62");
}
console.log(normalisasiHP("0812-3456-7890")); // → '6281234567890'
// 3. Password kuat: min 8 karakter, ada huruf kecil, huruf besar, angka, simbol.
// Tiap (?=...) adalah lookahead: "pastikan ada X di suatu tempat".
const polaPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/;
console.log(polaPassword.test("rahasia")); // → false
console.log(polaPassword.test("Rahasia123")); // → false (tanpa simbol)
console.log(polaPassword.test("Rahasia123!")); // → true
// Versi ramah pengguna: beri tahu syarat mana yang belum terpenuhi
function cekPassword(pw) {
const aturan = [
{ pola: /.{8,}/, pesan: "minimal 8 karakter" },
{ pola: /[a-z]/, pesan: "ada huruf kecil" },
{ pola: /[A-Z]/, pesan: "ada huruf besar" },
{ pola: /\d/, pesan: "ada angka" },
{ pola: /[^A-Za-z0-9]/, pesan: "ada simbol" },
];
return aturan.filter((a) => !a.pola.test(pw)).map((a) => a.pesan);
}
console.log(cekPassword("abc")); // → [ 'minimal 8 karakter', 'ada huruf besar', 'ada angka', 'ada simbol' ]
console.log(cekPassword("Rahasia123!")); // → []
Versi cekPassword yang kedua jauh lebih baik untuk pengguna. Satu regex raksasa hanya bisa bilang "salah", sedangkan daftar aturan kecil bisa bilang "kurang huruf besar". Pesan seperti itu yang membuat orang tidak kesal saat mengisi form.
Mencari dengan kata kunci dari pengguna
// Karakter spesial di input pengguna harus di-escape sebelum masuk RegExp
function escapeRegex(teks) {
return teks.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& = seluruh teks yang cocok
}
function sorot(kalimat, kata) {
const pola = new RegExp(escapeRegex(kata), "gi");
return kalimat.replace(pola, (cocok) => `[${cocok}]`);
}
console.log(sorot("Harga C++ dan c++ naik", "c++")); // → 'Harga [C++] dan [c++] naik'
Tanpa escapeRegex, input c++ akan membuat error karena + setelah + tidak valid sebagai pola.
Kesalahan umum
- Lupa anchor saat validasi.
/\d{10,13}/.test("abc0812345678901xyz")bernilaitrue. Untuk validasi isi form, hampir selalu bungkus pola dengan^...$. - Memakai
test()dengan flaggberulang kali. Regex ber-flaggmenyimpan posisi terakhir di propertilastIndex. Akibatnyaconst r = /a/g; r.test("a"); r.test("a");menghasilkantruelalufalse. Untuk validasi, jangan pakaig. Kalau terpaksa, reset denganr.lastIndex = 0. - Backslash tunggal di
new RegExp.new RegExp("\d")sama dengan/d/. Tulis"\\d", atau pakai literal kalau polanya tidak dinamis. - Mengira
match()dengangmasih memberi grup. Dengang,match()hanya mengembalikan array teks yang cocok, tanpa grup. Butuh grup dari semua kecocokan? PakaimatchAll(). - Tidak menangani
null."abc".match(/\d+/)[0]langsung error karena hasilnyanull. Cek dulu, atau pakai optional chaining:"abc".match(/\d+/)?.[0] ?? "tidak ada".
Latihan
- Mudah, Buat function
hanyaAngka(str)yang mengembalikantruejika string hanya berisi digit (minimal satu digit), selain itufalse. - Sedang, Diberikan teks
"Hubungi kami: 0812-1111-2222 atau +62 857 3333 4444, kantor 021-555-1234". Ambil semua nomor HP (bukan nomor kantor) lalu kembalikan dalam format62xxxtanpa spasi atau strip. - Menantang, Buat function
parseLog(log)yang menerima beberapa baris log seperti"[2026-10-23 08:15:02] ERROR login gagal user=budi"dan mengembalikan array object{ tanggal, jam, level, pesan }hanya untuk baris ber-levelERRORatauWARN. Gunakan named group danmatchAll.
Lihat solusi
// 1. Mudah
const hanyaAngka = (str) => /^\d+$/.test(str);
console.log(hanyaAngka("12345")); // → true
console.log(hanyaAngka("12a45")); // → false
console.log(hanyaAngka("")); // → false
// 2. Sedang
const teks = "Hubungi kami: 0812-1111-2222 atau +62 857 3333 4444, kantor 021-555-1234";
// Tangkap kandidat: awalan HP diikuti digit/spasi/strip
const kandidat = teks.match(/(?:\+62|62|0)\s?8[\d\s-]{8,16}\d/g) ?? [];
const nomorHP = kandidat.map((n) => n.replace(/[\s-]/g, "").replace(/^(?:\+62|0)/, "62"));
console.log(nomorHP); // → [ '6281211112222', '6285733334444' ]
// 3. Menantang
function parseLog(log) {
const pola =
/^\[(?<tanggal>\d{4}-\d{2}-\d{2}) (?<jam>\d{2}:\d{2}:\d{2})\] (?<level>ERROR|WARN) (?<pesan>.+)$/gm;
return [...log.matchAll(pola)].map((m) => ({ ...m.groups }));
}
const log = `[2026-10-23 08:15:02] ERROR login gagal user=budi
[2026-10-23 08:16:10] INFO user=sari masuk
[2026-10-23 08:17:45] WARN disk tersisa 10%`;
console.log(parseLog(log));
// → [
// { tanggal: '2026-10-23', jam: '08:15:02', level: 'ERROR', pesan: 'login gagal user=budi' },
// { tanggal: '2026-10-23', jam: '08:17:45', level: 'WARN', pesan: 'disk tersisa 10%' }
// ]
Pada soal 2, bagian \s? setelah awalan menampung spasi di +62 857. Kunci soal 3 adalah flag m, sehingga ^ dan $ berlaku per baris, dan alternatif (ERROR|WARN) yang otomatis membuang baris INFO.
Ringkasan
- Regex adalah pola teks. Buat dengan literal
/pola/flagataunew RegExp("pola", "flag"), dan ingat backslash dobel pada versi string. - Flag
g(semua),i(abaikan huruf besar/kecil),m(per baris),u(Unicode/emoji) mengubah cara pencarian. - Character class (
[a-z],\d,\w,\s) mewakili satu karakter; quantifier (*,+,?,{n,m}) menentukan jumlahnya;^dan$mengunci awal dan akhir. - Group
( )menangkap bagian teks, named group(?<nama>)membuatnya terbaca, lookahead(?=)berguna untuk syarat password. testuntuk validasi,match/matchAlluntuk mengambil data,replaceuntuk membersihkan dan memformat.- Regex email hanya penyaring kasar. Validasi yang pasti tetap lewat verifikasi di sisi server.