JavaScript Modern untuk Pemula: dari Dasar ke DOM
Dasar-dasar JavaScript yang benar-benar dipakai hari ini, langsung dipraktikkan untuk membuat halaman dengan pencarian dan keranjang belanja.
Oleh Afa Hernandes15 menit baca
Daftar isi
- Sebelum mulai
- Dua tempat menjalankan JavaScript
- Variabel: const dan let
- Function dan arrow function
- Array dan method yang paling sering dipakai
- Object: destructuring, spread, dan nilai kosong
- Langkah 1: Siapkan halaman
- Langkah 2: Ambil elemen dari halaman
- Langkah 3: Tampilkan kartu menu
- Langkah 4: Keranjang belanja
- Langkah 5: Pencarian langsung
- Langkah 6: Muat data dengan fetch
- Langkah 7: Jalankan lewat server lokal
- Hasil
- Kode JavaScript lengkap
- Error yang sering muncul
- Langkah berikutnya: rilis dan amankan
- Pertanyaan yang sering diajukan
- JavaScript dan Java itu sama?
- Perlu belajar jQuery?
- Titik koma di akhir baris wajib?
- Kapan saya mulai belajar framework seperti React?
- Kesimpulan
HTML menentukan isi halaman dan CSS menentukan tampilannya, tapi halaman yang hanya berisi keduanya tidak bisa merespons apa pun. Tombol yang menambah pesanan, kolom pencarian yang langsung menyaring hasil, atau data yang dimuat tanpa memuat ulang halaman, semuanya dikerjakan oleh JavaScript.
Di tutorial ini kamu akan mempelajari dasar JavaScript modern yang benar-benar dipakai hari ini, lalu langsung memakainya untuk membuat halaman menu Kopi Senja dengan pencarian dan keranjang belanja. Datanya dimuat dari file JSON, seperti aplikasi sungguhan yang mengambil data dari server.
Sebelum mulai
- Paham dasar logika: variabel, percabangan, perulangan, dan function. Contoh di jalur Mulai Ngoding memakai Python, tapi konsepnya sama.
- Sudah membaca Flexbox dan Grid. Halaman di tutorial ini memakai tampilan yang mirip.
- Node.js versi LTS dari nodejs.org. Node dipakai untuk menjalankan contoh kode di terminal dan menyalakan server lokal.
- Diuji dengan Node 24 dan Chrome.
Cek instalasi Node di terminal:
node -vKalau muncul nomor versi (misalnya v24.22.0), Node siap dipakai.
Dua tempat menjalankan JavaScript
JavaScript bisa berjalan di dua tempat:
- Browser. Tekan
F12di Chrome, buka tab Console, lalu ketik1 + 2dan tekan Enter. Di sini juga semua pesan error halamanmu muncul, jadi biasakan membukanya. - Node.js. Simpan kode di file
.js, lalu jalankan dengannode nama-file.jsdi terminal. Cara ini yang dipakai untuk contoh-contoh dasar di bawah.
Variabel: const dan let
const namaKedai = "Kopi Senja";let jumlahPesanan = 0;jumlahPesanan = jumlahPesanan + 1;
console.log(`${namaKedai}: ${jumlahPesanan} pesanan`);Kopi Senja: 1 pesananconstuntuk nilai yang tidak akan diganti. Pakai ini sebagai pilihan pertama.letuntuk nilai yang memang akan berubah, seperti penghitung.console.log()mencetak nilai, sama sepertiprint()di Python.
Kalau kamu mencoba mengganti nilai const, JavaScript langsung menolak:
const pajak = 0.11;pajak = 0.12;TypeError: Assignment to constant variable.Di tutorial lama kamu akan menemukan var. Hindari: var punya aturan cakupan (scope) yang membingungkan dan sudah digantikan oleh const dan let.
Baris terakhir di dasar.js memakai template literal: teks yang diapit tanda backtick (`), bukan kutip biasa. Nilai variabel disisipkan dengan ${…}, mirip f-string di Python.
Function dan arrow function
function formatRupiah(nilai) { return "Rp" + nilai.toLocaleString("id-ID");}
const tambahPajak = (harga) => Math.round(harga * 1.11);
console.log(formatRupiah(18000));console.log(formatRupiah(tambahPajak(18000)));Rp18.000Rp19.980tambahPajak ditulis sebagai arrow function: parameter, tanda =>, lalu hasilnya. Kalau isinya hanya satu ekspresi, kata return dan kurung kurawal boleh dihilangkan. Bentuk ini sangat sering dipakai sebagai function kecil yang dioper ke function lain, seperti di bagian berikutnya.
toLocaleString("id-ID") memformat angka sesuai aturan Indonesia, dengan titik sebagai pemisah ribuan.
Array dan method yang paling sering dipakai
Data di aplikasi web hampir selalu berbentuk array berisi object: daftar produk, daftar pesanan, daftar pengguna.
const menu = [ { id: 1, nama: "Kopi susu", harga: 18000, kategori: "kopi" }, { id: 2, nama: "Americano", harga: 15000, kategori: "kopi" }, { id: 3, nama: "Matcha latte", harga: 22000, kategori: "non-kopi" }, { id: 4, nama: "Croissant", harga: 25000, kategori: "makanan" },];
const namaSaja = menu.map((item) => item.nama);const kopi = menu.filter((item) => item.kategori === "kopi");const croissant = menu.find((item) => item.id === 4);const totalSemua = menu.reduce((total, item) => total + item.harga, 0);
console.log(namaSaja);console.log(kopi.length, "menu kopi");console.log(croissant.nama);console.log(totalSemua);[ 'Kopi susu', 'Americano', 'Matcha latte', 'Croissant' ]2 menu kopiCroissant80000| Method | Fungsinya | Hasilnya |
|---|---|---|
map |
mengubah setiap item | array baru, jumlahnya sama |
filter |
menyaring item yang memenuhi syarat | array baru, bisa lebih sedikit |
find |
mencari item pertama yang cocok | satu item, atau undefined kalau tidak ada |
reduce |
menggabungkan semua item jadi satu nilai | satu nilai, misalnya total |
Keempatnya menerima function sebagai argumen, dan di situlah arrow function dipakai. Perhatikan juga ===: di JavaScript, selalu pakai tiga tanda sama dengan untuk membandingkan. == (dua tanda) diam-diam mengubah tipe data, sehingga "1" == 1 dianggap benar.
Object: destructuring, spread, dan nilai kosong
const produk = { nama: "Kopi susu", harga: 18000, stok: 0 };
const { nama, harga } = produk;console.log(nama, harga);
const produkDiskon = { ...produk, harga: 15000 };console.log(produkDiskon);console.log(produk.harga);
const pelanggan = { nama: "Sari" };console.log(pelanggan.alamat?.kota);console.log(pelanggan.alamat?.kota ?? "Alamat belum diisi");
console.log(produk.stok || "Stok tidak diketahui");console.log(produk.stok ?? "Stok tidak diketahui");Kopi susu 18000{ nama: 'Kopi susu', harga: 15000, stok: 0 }18000undefinedAlamat belum diisiStok tidak diketahui0- Destructuring (
const { nama, harga } = produk) mengambil beberapa properti sekaligus ke variabel dengan nama yang sama. - Spread (
{ ...produk, harga: 15000 }) menyalin semua properti ke object baru, lalu menimpa yang perlu diubah. Object aslinya tidak berubah, terlihat dariproduk.hargayang tetap18000. - Optional chaining (
?.) mencegah error saat properti di tengahnya tidak ada. Tanpa?.,pelanggan.alamat.kotalangsung error karenaalamattidak ada. - Nullish coalescing (
??) memberi nilai cadangan hanya kalau nilainyanullatauundefined.
Dua baris terakhir menunjukkan bedanya dengan ||. Stok 0 adalah data yang sah (barangnya habis), tapi || menganggap 0 sama dengan “kosong” lalu menggantinya. ?? tidak melakukan itu, jadi lebih aman untuk angka.
Sekarang saatnya memakai semua ini di halaman sungguhan.
Langkah 1: Siapkan halaman
Buat folder menu-kopi berisi empat file. Pertama, HTML-nya:
<!doctype html><html lang="id"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Menu Kopi Senja</title> <link rel="stylesheet" href="style.css"> <script type="module" src="app.js"></script></head><body> <main class="menu"> <h1>Menu Kopi Senja</h1>
<label class="search"> Cari menu <input id="cari" type="search" placeholder="mis. latte" autocomplete="off"> </label>
<p id="status" class="status">Memuat menu…</p> <div id="daftar-menu" class="card-grid"></div>
<aside class="keranjang"> <h2>Pesananmu</h2> <ul id="isi-keranjang"></ul> <p class="total">Total: <strong id="total">Rp0</strong></p> </aside> </main></body></html>Daftar menunya sengaja kosong (<div id="daftar-menu">). Isinya nanti dibuat oleh JavaScript dari data.
<script type="module"> memuat app.js sebagai module. Module JavaScript otomatis dijalankan setelah seluruh HTML selesai dibaca, jadi kodenya aman mencari elemen di halaman. Module juga bisa memakai import dan export untuk memecah kode ke beberapa file.
Lalu datanya, dalam format JSON (JavaScript Object Notation), format yang juga dipakai hampir semua API:
[ { "id": 1, "nama": "Kopi susu", "harga": 18000, "kategori": "kopi" }, { "id": 2, "nama": "Americano", "harga": 15000, "kategori": "kopi" }, { "id": 3, "nama": "Matcha latte", "harga": 22000, "kategori": "non-kopi" }, { "id": 4, "nama": "Croissant", "harga": 25000, "kategori": "makanan" }]Dan tampilannya. Tekniknya sama dengan di tutorial Flexbox dan Grid:
*,*::before,*::after { box-sizing: border-box;}
body { margin: 0; font-family: system-ui, sans-serif; line-height: 1.6; color: #1f2328; background: #fbf9f6;}
.menu { max-width: 1100px; margin: 0 auto; padding: 2rem 1.5rem 4rem;}
.search { display: flex; flex-direction: column; gap: 0.25rem; max-width: 360px; margin-bottom: 1rem; font-weight: 600;}
.search input { padding: 0.6rem 0.8rem; border: 1px solid #d0c4b8; border-radius: 10px; font: inherit; font-weight: 400;}
.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem;}
.card { display: flex; flex-direction: column; gap: 0.5rem; padding: 1.25rem; border: 1px solid #e7dfd6; border-radius: 16px; background: #fff;}
.card h3,.card p { margin: 0;}
.card .price { font-weight: 700; color: #b45309;}
.card button { margin-top: auto; padding: 0.5rem; border: 0; border-radius: 999px; background: #b45309; color: #fff; font: inherit; font-weight: 600; cursor: pointer;}
.keranjang { margin-top: 2rem; padding: 1.25rem; border-radius: 16px; background: #3b2a20; color: #f3e3d3;}
.keranjang h2 { margin-top: 0;}
.status:empty { display: none;}Grid di sini memakai auto-fill, bukan auto-fit. Hasil pencarian bisa tinggal satu kartu, dan auto-fill menjaga kartu itu tetap selebar satu kolom, tidak melebar selebar layar.
File keempat, app.js, kita tulis bertahap di langkah berikutnya.
Langkah 2: Ambil elemen dari halaman
Browser menyediakan DOM (Document Object Model): representasi halaman HTML sebagai object yang bisa dibaca dan diubah oleh JavaScript. Langkah pertama hampir selalu sama, yaitu mengambil elemen yang dibutuhkan:
const daftarMenu = document.querySelector("#daftar-menu");const inputCari = document.querySelector("#cari");const status = document.querySelector("#status");const isiKeranjang = document.querySelector("#isi-keranjang");const totalEl = document.querySelector("#total");
let menu = [];const keranjang = [];
const formatRupiah = (nilai) => "Rp" + nilai.toLocaleString("id-ID");document.querySelector() menerima selektor CSS, sama persis dengan yang kamu tulis di file CSS: #cari untuk id, .card untuk class. Hasilnya elemen pertama yang cocok, atau null kalau tidak ada.
menu memakai let karena isinya nanti diganti dengan data dari JSON. keranjang memakai const karena array-nya tetap sama. Yang berubah hanya isinya, dan const membolehkan itu.
Langkah 3: Tampilkan kartu menu
function buatKartu(item) { const kartu = document.createElement("article"); kartu.className = "card";
const judul = document.createElement("h3"); judul.textContent = item.nama;
const harga = document.createElement("p"); harga.className = "price"; harga.textContent = formatRupiah(item.harga);
const tombol = document.createElement("button"); tombol.type = "button"; tombol.textContent = "Tambah"; tombol.addEventListener("click", () => tambahKeKeranjang(item));
kartu.append(judul, harga, tombol); return kartu;}
function tampilkanMenu(daftar) { daftarMenu.replaceChildren(...daftar.map(buatKartu)); status.textContent = daftar.length === 0 ? "Menu tidak ditemukan." : "";}document.createElement()membuat elemen baru, danappend()memasukkannya ke elemen lain.addEventListener("click", …)mendaftarkan function yang dijalankan setiap kali tombol diklik. Inilah cara halaman merespons pengguna.daftar.map(buatKartu)mengubah array data menjadi array elemen, lalureplaceChildren()mengganti seluruh isidaftarMenudengan elemen-elemen itu. Tanda...(spread) membuka array-nya menjadi argumen satu per satu.- Baris
statusmemakai operator ternary (syarat ? kalauBenar : kalauSalah), bentuk singkatif/elseuntuk memilih satu dari dua nilai.
Kenapa tidak menulis HTML sebagai teks lewat innerHTML? Karena textContent selalu memperlakukan isinya sebagai teks biasa. Kalau suatu saat nama menu berasal dari input pengguna dan isinya <img src=x onerror="…">, textContent menampilkannya apa adanya sebagai tulisan, sedangkan innerHTML mengubahnya menjadi elemen sungguhan dan menjalankan kode di dalam onerror. Celah ini disebut XSS, dan dibahas lengkap di XSS dan CSRF. Biasakan textContent sejak awal.
Langkah 4: Keranjang belanja
function tambahKeKeranjang(item) { const sudahAda = keranjang.find((baris) => baris.id === item.id); if (sudahAda) { sudahAda.jumlah += 1; } else { keranjang.push({ ...item, jumlah: 1 }); } tampilkanKeranjang();}
function tampilkanKeranjang() { const baris = keranjang.map((item) => { const li = document.createElement("li"); li.textContent = `${item.jumlah}x ${item.nama}: ${formatRupiah(item.harga * item.jumlah)}`; return li; }); isiKeranjang.replaceChildren(...baris);
const total = keranjang.reduce((jumlah, item) => jumlah + item.harga * item.jumlah, 0); totalEl.textContent = formatRupiah(total);}Semua yang kamu pelajari di bagian dasar dipakai di sini: find mencari apakah menu itu sudah ada di keranjang, spread ({ ...item, jumlah: 1 }) membuat salinan data menu dengan tambahan jumlah, map mengubah isi keranjang jadi baris daftar, dan reduce menghitung totalnya.
Polanya juga patut diingat: ubah datanya dulu, lalu tampilkan ulang dari data. Function tampilkanKeranjang() selalu menggambar ulang keranjang dari array keranjang, sehingga tampilan tidak pernah berbeda dengan datanya. Framework seperti React dan Svelte dibangun di atas ide yang sama.
Langkah 5: Pencarian langsung
inputCari.addEventListener("input", () => { const kata = inputCari.value.trim().toLowerCase(); const hasil = menu.filter((item) => item.nama.toLowerCase().includes(kata)); tampilkanMenu(hasil);});Event input terpicu setiap kali isi kolom berubah, jadi hasilnya tersaring sambil kamu mengetik. trim() membuang spasi di awal dan akhir, dan toLowerCase() membuat pencarian tidak membedakan huruf besar-kecil, sehingga mengetik MATCHA tetap menemukan “Matcha latte”.
Langkah 6: Muat data dengan fetch
async function muatMenu() { try { const respons = await fetch("menu.json"); if (!respons.ok) { throw new Error(`HTTP ${respons.status}`); } menu = await respons.json(); tampilkanMenu(menu); } catch (error) { status.textContent = "Menu gagal dimuat. Coba muat ulang halaman."; console.error(error); }}
muatMenu();fetch() meminta file atau data dari server. Permintaan lewat jaringan butuh waktu, jadi hasilnya tidak langsung ada:
asyncmenandai function yang berisi pekerjaan yang perlu ditunggu.awaitmenunggu sampai hasilnya datang, lalu lanjut ke baris berikutnya. Kodenya tetap terbaca dari atas ke bawah.respons.okbernilaifalsekalau server menjawab dengan error seperti 404.fetchtidak otomatis gagal untuk status seperti itu, jadi kamu harus memeriksanya sendiri.try/catchmenangkap semua kegagalan, baik koneksi putus maupun status error, lalu menampilkan pesan yang jelas untuk pengguna. Detail teknisnya tetap dicatat di Console lewatconsole.error().
Ganti menu.json dengan alamat API sungguhan, dan kodenya tetap sama. Begitulah aplikasi web mengambil data dari server.
Langkah 7: Jalankan lewat server lokal
Kalau index.html dibuka dengan klik dua kali, menunya tidak akan pernah muncul. Browser memblokir module dan fetch dari alamat file:// demi keamanan. Kamu perlu server lokal. Dari folder menu-kopi, jalankan:
npx serve .npx mengunduh dan menjalankan paket serve tanpa perlu menginstalnya permanen. Saat pertama kali, kamu mungkin diminta konfirmasi (Ok to proceed? (y)); ketik y. Buka alamat yang ditampilkan di terminal, biasanya http://localhost:3000. Hentikan server dengan Ctrl+C.
Kalau kamu memakai VS Code, ekstensi seperti Live Server juga bisa dipakai. Yang penting, alamatnya diawali http://, bukan file://.
Hasil
Klik Tambah di kopi susu dua kali dan di croissant sekali. Keranjang dan totalnya langsung diperbarui:

Ketik latte di kolom pencarian. Hanya Matcha latte yang tersisa, dan isi keranjang tidak hilang:

Kalau menu.json tidak ditemukan, halaman menampilkan “Menu gagal dimuat. Coba muat ulang halaman.” dan Console mencatat Error: HTTP 404. Semua perilaku ini sudah diuji di Chrome.
Kode JavaScript lengkap
const daftarMenu = document.querySelector("#daftar-menu");const inputCari = document.querySelector("#cari");const status = document.querySelector("#status");const isiKeranjang = document.querySelector("#isi-keranjang");const totalEl = document.querySelector("#total");
let menu = [];const keranjang = [];
const formatRupiah = (nilai) => "Rp" + nilai.toLocaleString("id-ID");
function buatKartu(item) { const kartu = document.createElement("article"); kartu.className = "card";
const judul = document.createElement("h3"); judul.textContent = item.nama;
const harga = document.createElement("p"); harga.className = "price"; harga.textContent = formatRupiah(item.harga);
const tombol = document.createElement("button"); tombol.type = "button"; tombol.textContent = "Tambah"; tombol.addEventListener("click", () => tambahKeKeranjang(item));
kartu.append(judul, harga, tombol); return kartu;}
function tampilkanMenu(daftar) { daftarMenu.replaceChildren(...daftar.map(buatKartu)); status.textContent = daftar.length === 0 ? "Menu tidak ditemukan." : "";}
function tambahKeKeranjang(item) { const sudahAda = keranjang.find((baris) => baris.id === item.id); if (sudahAda) { sudahAda.jumlah += 1; } else { keranjang.push({ ...item, jumlah: 1 }); } tampilkanKeranjang();}
function tampilkanKeranjang() { const baris = keranjang.map((item) => { const li = document.createElement("li"); li.textContent = `${item.jumlah}x ${item.nama}: ${formatRupiah(item.harga * item.jumlah)}`; return li; }); isiKeranjang.replaceChildren(...baris);
const total = keranjang.reduce((jumlah, item) => jumlah + item.harga * item.jumlah, 0); totalEl.textContent = formatRupiah(total);}
inputCari.addEventListener("input", () => { const kata = inputCari.value.trim().toLowerCase(); const hasil = menu.filter((item) => item.nama.toLowerCase().includes(kata)); tampilkanMenu(hasil);});
async function muatMenu() { try { const respons = await fetch("menu.json"); if (!respons.ok) { throw new Error(`HTTP ${respons.status}`); } menu = await respons.json(); tampilkanMenu(menu); } catch (error) { status.textContent = "Menu gagal dimuat. Coba muat ulang halaman."; console.error(error); }}
muatMenu();Error yang sering muncul
Semua pesan di bawah muncul di tab Console DevTools (F12).
Access to script at ‘file:///…/app.js’ from origin ‘null’ has been blocked by CORS policy
Halaman dibuka langsung dari file (alamatnya diawali file://). Browser tidak mengizinkan module dimuat dari sana. Jalankan lewat server lokal seperti di Langkah 7.
TypeError: Cannot read properties of null (reading ‘addEventListener’)
querySelector() tidak menemukan elemennya, sehingga hasilnya null. Periksa apakah selektornya sama persis dengan id di HTML (#cari untuk id="cari", termasuk huruf besar-kecilnya). Kalau kamu memakai <script> biasa tanpa type="module" di dalam <head>, skripnya berjalan sebelum elemennya ada. Tambahkan type="module" atau defer.
TypeError: Assignment to constant variable.
Kamu mengganti nilai variabel yang dibuat dengan const. Kalau nilainya memang perlu berubah, ganti dengan let.
Error: HTTP 404
Pesan dari kode kita sendiri: fetch tidak menemukan menu.json. Pastikan nama file dan letaknya benar, satu folder dengan index.html.
Total menjadi NaN atau angkanya menyambung, misalnya 1800018000.
Harganya terbaca sebagai teks, bukan angka. Ini sering terjadi pada nilai dari <input>, yang selalu berupa teks. Ubah dulu dengan Number(nilai) sebelum dihitung. Kesalahan seperti ini bisa ditangkap otomatis oleh TypeScript.
Langkah berikutnya: rilis dan amankan
Halaman ini tetap statis: hanya file HTML, CSS, JavaScript, dan JSON, tanpa backend. Artinya bisa langsung online gratis lewat deploy pertama ke Cloudflare Pages. Unggah folder menu-kopi, lalu fitur pencarian dan keranjangnya berjalan sama seperti di laptop.
Dua hal keamanan yang perlu diingat sejak sekarang:
- Semua kode JavaScript di browser bisa dibaca siapa saja lewat DevTools. Jangan pernah menaruh API key, password, atau data rahasia di dalamnya. Caranya ada di environment variable dan secrets.
- Pengguna bisa mengubah apa pun yang terjadi di browser, termasuk harga di keranjang. Di aplikasi sungguhan, total belanja harus dihitung ulang di server, bukan dipercaya dari browser.
Pertanyaan yang sering diajukan
JavaScript dan Java itu sama?
Tidak. Keduanya bahasa yang berbeda, dengan cara kerja dan kegunaan yang berbeda. Kemiripan namanya hanya urusan sejarah pemasaran.
Perlu belajar jQuery?
Tidak untuk proyek baru. Banyak fitur yang dulu menjadi alasan memakai jQuery, seperti memilih elemen dan mengambil data, sekarang sudah ada langsung di browser lewat querySelector dan fetch. Kamu cukup bisa membacanya kalau menemukannya di proyek lama.
Titik koma di akhir baris wajib?
Secara teknis JavaScript bisa menambahkannya sendiri, tapi ada beberapa kasus yang hasilnya mengejutkan. Pilih satu gaya dan pakai konsisten. Kode di tutorial ini selalu memakai titik koma.
Kapan saya mulai belajar framework seperti React?
Setelah kamu nyaman dengan semua yang ada di tutorial ini: array method, DOM, event, dan fetch. Framework mengotomatiskan pola “ubah data, lalu tampilkan ulang” dari Langkah 4, dan jauh lebih mudah dipahami kalau kamu sudah pernah melakukannya secara manual.
Kesimpulan
JavaScript modern cukup dikuasai dari beberapa hal: const dan let, arrow function, map/filter/find/reduce, object dengan spread dan ??, DOM dan event, serta fetch dengan async/await. Dengan itu kamu sudah membuat halaman yang memuat data, merespons klik, dan menyaring hasil secara langsung.
Kamu mungkin sudah merasakan satu kelemahannya: JavaScript baru memberi tahu kesalahan saat program dijalankan. Langkah berikutnya di jalur Web Developer Modern, TypeScript, menangkap banyak kesalahan itu sebelum kode dijalankan.



