Menghubungkan Frontend ke Backend dengan Fetch API
Halo, Gabuters!
Pada artikel sebelumnya, kita berhasil membuat REST API CRUD menggunakan JavaScript tanpa framework.
API tersebut sudah mendukung GET, POST, PUT, dan DELETE. Kita juga belajar membaca request body, memvalidasi data, dan mengirim response JSON.
Namun, selama ini kita menguji API menggunakan browser dan curl.
Bagaimana jika kita ingin menampilkan daftar artikel langsung di halaman HTML?
Bagaimana jika pengunjung bisa menambahkan artikel melalui formulir?
Dan bagaimana cara menampilkan pesan ketika server sedang bermasalah?
Untuk menjawabnya, kita akan belajar Fetch API, yaitu antarmuka JavaScript yang dapat digunakan untuk melakukan HTTP request dari browser.
Pada akhir artikel, kita akan membuat halaman sederhana yang dapat:
- Mengambil daftar artikel dari backend.
- Menampilkan data artikel.
- Menambahkan artikel melalui formulir.
- Menampilkan indikator loading.
- Menangani error jaringan dan respons HTTP.
- Memperbarui tampilan tanpa memuat ulang seluruh halaman.
Kita tetap menggunakan JavaScript murni, tanpa framework frontend maupun backend.
1. Mengapa Frontend Perlu Berkomunikasi dengan Backend?
Bayangkan kita mempunyai halaman daftar artikel.
Jika seluruh artikel ditulis langsung dalam HTML, setiap perubahan data mungkin mengharuskan kita mengubah kode halaman.
Namun, jika artikel disediakan melalui API, frontend dapat meminta data terbaru dari backend.
Alurnya seperti ini:
Pengguna membuka halaman | vJavaScript menjalankan fetch() | | HTTP GET vBackend API | | Response JSON vJavaScript membaca response | vHalaman menampilkan artikelDengan cara ini, frontend bertanggung jawab menampilkan data, sedangkan backend bertanggung jawab menyediakan dan memprosesnya.
Pemisahan tersebut memudahkan kita memahami peran masing-masing komponen.
2. Apa Itu Fetch API?
Fetch API adalah antarmuka yang disediakan lingkungan seperti browser untuk melakukan request dan menerima response.
Di browser modern, kita dapat menggunakannya melalui function fetch().
Contoh sederhana:
fetch("/api/artikel");Kode tersebut mengirim request ke endpoint /api/artikel pada origin yang sama dengan halaman.
Namun, fetch() tidak langsung menghasilkan data artikel.
Function tersebut mengembalikan Promise.
Promise merepresentasikan hasil operasi asynchronous yang mungkin tersedia kemudian.
Karena komunikasi jaringan membutuhkan waktu, JavaScript tidak harus menghentikan seluruh interaksi halaman sambil menunggu respons.
3. Mengenal Promise
Sebelum menggunakan Fetch API lebih jauh, kita perlu memahami Promise.
Promise mempunyai tiga keadaan utama:
pending: operasi masih berlangsung.fulfilled: operasi berhasil diselesaikan.rejected: operasi gagal.
Contohnya:
const janji = new Promise((resolve) => { setTimeout(() => { resolve("Data berhasil diperoleh."); }, 1000);});
janji.then((hasil) => { console.log(hasil);});Setelah sekitar satu detik, console menampilkan:
Data berhasil diperoleh.Pada contoh tersebut, resolve() menyelesaikan Promise dengan hasil tertentu.
Kita juga dapat menangani penolakan Promise menggunakan .catch().
Contohnya:
const janji = Promise.reject( new Error("Terjadi kesalahan."));
janji .then((hasil) => { console.log(hasil); }) .catch((error) => { console.error(error.message); });Hasilnya:
Terjadi kesalahan.Promise sangat sering digunakan ketika bekerja dengan API.
4. Menggunakan fetch() dengan then()
Sekarang kita coba mengambil data artikel.
Misalnya, backend menyediakan:
GET http://localhost:3000/api/artikelKita bisa menulis:
fetch("http://localhost:3000/api/artikel") .then((response) => { return response.json(); }) .then((data) => { console.log(data); }) .catch((error) => { console.error("Gagal mengambil data:", error); });Mari kita pahami alurnya.
Pertama, fetch() mengirim HTTP request.
Kemudian .then() menerima objek Response ketika Promise dari fetch() berhasil diselesaikan.
Method:
response.json()Membaca body response dan menguraikannya sebagai JSON secara asynchronous.
Karena response.json() juga mengembalikan Promise, kita menggunakan .then() berikutnya untuk mendapatkan hasilnya.
Jika terjadi kegagalan jaringan atau proses Promise ditolak, .catch() dapat menangani error tersebut.
Namun, ada satu hal penting.
Fetch tidak otomatis menolak Promise hanya karena server mengembalikan status HTTP 404 atau 500.
Kita perlu memeriksa status respons sendiri.
5. Memeriksa response.ok
Objek Response mempunyai properti:
response.okNilainya true ketika status HTTP berada dalam rentang 200 sampai 299.
Contohnya:
fetch("http://localhost:3000/api/artikel") .then((response) => { if (!response.ok) { throw new Error( `Request gagal dengan status ${response.status}` ); }
return response.json(); }) .then((data) => { console.log(data); }) .catch((error) => { console.error(error.message); });Dengan pemeriksaan tersebut, respons seperti 404 atau 500 akan diperlakukan sebagai error oleh alur kode kita.
Ini penting karena kegagalan HTTP dan kegagalan jaringan merupakan dua kondisi yang berbeda.
6. Mengenal async dan await
Penulisan Promise menggunakan .then() valid dan sering digunakan.
Namun, ketika alurnya semakin panjang, sebagian pengembang lebih nyaman menggunakan async/await.
Kata kunci async digunakan untuk mendeklarasikan function asynchronous.
Kata kunci await digunakan untuk menunggu penyelesaian Promise di dalam konteks yang mendukungnya.
Contohnya:
async function ambilArtikel() { const response = await fetch( "http://localhost:3000/api/artikel" );
if (!response.ok) { throw new Error( `Request gagal dengan status ${response.status}` ); }
const data = await response.json();
console.log(data);}
ambilArtikel().catch((error) => { console.error(error.message);});Alurnya terlihat lebih menyerupai kode berurutan.
Namun, operasi tersebut tetap asynchronous.
await tidak berarti seluruh browser berhenti bekerja sampai request selesai.
7. Menangani Error dengan try…catch
Ketika menggunakan async/await, kita sering memakai try...catch.
Contohnya:
async function ambilArtikel() { try { const response = await fetch( "http://localhost:3000/api/artikel" );
if (!response.ok) { throw new Error( `HTTP ${response.status}` ); }
const data = await response.json();
console.log(data); } catch (error) { console.error( "Gagal mengambil artikel:", error.message ); }}
ambilArtikel();Blok try berisi operasi yang mungkin menghasilkan error.
Blok catch menangani error tersebut.
Error dapat berasal dari:
- Kegagalan jaringan.
- Request yang dibatalkan.
- Respons HTTP yang kita perlakukan sebagai error.
- Body response yang tidak valid sebagai JSON.
- Kesalahan lain ketika memproses data.
Dalam aplikasi nyata, pesan yang ditampilkan kepada pengguna sebaiknya mudah dipahami dan tidak membocorkan detail internal server.
8. Mengenal Same-Origin Policy dan CORS
Sebelum membuat halaman frontend, ada konsep penting yang perlu dipahami: origin.
Origin terdiri dari skema, hostname, dan port.
Contohnya:
http://localhost:3000Dan:
http://localhost:5500Merupakan origin berbeda karena port-nya berbeda.
Begitu juga:
http://localhost:3000Dan:
http://127.0.0.1:3000Merupakan origin berbeda karena hostname-nya berbeda.
Browser menerapkan kebijakan keamanan yang disebut same-origin policy.
Untuk request lintas origin, mekanisme CORS (Cross-Origin Resource Sharing) memungkinkan server menyatakan origin mana yang diizinkan membaca respons melalui JavaScript browser.
Misalnya, frontend berjalan pada:
http://localhost:5500Sedangkan backend berjalan pada:
http://localhost:3000Jika server tidak mengizinkan origin frontend melalui CORS, browser dapat memblokir akses JavaScript terhadap respons.
CORS merupakan mekanisme yang diterapkan browser.
CORS bukan pengganti autentikasi, otorisasi, atau validasi data.
Karena itu, jangan menganggap API aman hanya karena CORS sudah dikonfigurasi.
9. Cara Menghindari Masalah CORS Saat Belajar
Untuk latihan awal, kita akan menggunakan pendekatan yang lebih sederhana.
Frontend dan API dijalankan melalui satu HTTP server yang sama.
Dengan begitu, halaman HTML dan endpoint API menggunakan origin yang sama.
Struktur proyek latihan kita:
belajar-fetch-api/├── server.js└── public/ ├── index.html ├── style.css └── script.jsNode.js akan melayani halaman HTML, CSS, dan JavaScript sekaligus menyediakan endpoint API.
Kita tidak membutuhkan server frontend terpisah.
Keuntungannya:
- Tidak perlu mengonfigurasi CORS untuk latihan ini.
- Tidak perlu memasang framework.
- Semua request menggunakan satu origin.
- Lebih mudah memahami komunikasi frontend dan backend.
10. Menyiapkan Proyek
Buat folder:
belajar-fetch-api/Di dalamnya, buat file dan folder sesuai struktur sebelumnya.
Pastikan Node.js sudah tersedia:
node --versionUntuk latihan ini, kita akan menggunakan modul bawaan:
require("node:http");require("node:fs/promises");require("node:path");Modul tersebut sudah tersedia pada Node.js.
Kita tidak perlu menjalankan npm install.
11. Membuat Backend API dan Server Frontend
Buka file server.js.
Masukkan kode lengkap berikut:
const http = require("node:http");const fs = require("node:fs/promises");const path = require("node:path");
const HOST = "127.0.0.1";const PORT = 3000;const BATAS_BODY = 10 * 1024;
const folderPublic = path.join(__dirname, "public");
let artikel = [ { id: 1, judul: "Belajar HTML", kategori: "HTML" }, { id: 2, judul: "Belajar CSS", kategori: "CSS" }, { id: 3, judul: "Belajar JavaScript", kategori: "JavaScript" }];
let idBerikutnya = 4;
function kirimJSON(res, statusCode, data) { res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
res.end(JSON.stringify(data));}
function buatError(statusCode, pesan) { const error = new Error(pesan); error.statusCode = statusCode; return error;}
async function bacaJSON(req) { const tipeKonten = req.headers["content-type"] || "";
if (!/^application\/json(?:\s*;|$)/i.test(tipeKonten)) { throw buatError( 415, "Gunakan Content-Type: application/json." ); }
const chunks = []; let totalUkuran = 0; let terlaluBesar = false;
for await (const chunk of req) { totalUkuran += chunk.length;
if (totalUkuran > BATAS_BODY) { terlaluBesar = true; }
if (!terlaluBesar) { chunks.push(chunk); } }
if (terlaluBesar) { throw buatError(413, "Ukuran request terlalu besar."); }
const body = Buffer.concat(chunks).toString("utf8");
try { return JSON.parse(body); } catch { throw buatError(400, "Format JSON tidak valid."); }}
function validasiArtikel(data) { return ( data !== null && typeof data === "object" && !Array.isArray(data) && typeof data.judul === "string" && data.judul.trim().length > 0 && data.judul.trim().length <= 150 && typeof data.kategori === "string" && data.kategori.trim().length > 0 && data.kategori.trim().length <= 50 );}
async function kirimFile(res, namaFile, contentType) { const lokasi = path.join(folderPublic, namaFile); const isi = await fs.readFile(lokasi);
res.writeHead(200, { "Content-Type": contentType });
res.end(isi);}
async function tanganiRequest(req, res) { const url = new URL(req.url, "http://localhost"); const pathname = url.pathname; const method = req.method;
if (method === "GET" && pathname === "/") { await kirimFile( res, "index.html", "text/html; charset=utf-8" ); return; }
if (method === "GET" && pathname === "/style.css") { await kirimFile( res, "style.css", "text/css; charset=utf-8" ); return; }
if (method === "GET" && pathname === "/script.js") { await kirimFile( res, "script.js", "text/javascript; charset=utf-8" ); return; }
if (method === "GET" && pathname === "/api/artikel") { kirimJSON(res, 200, artikel); return; }
if (method === "POST" && pathname === "/api/artikel") { const data = await bacaJSON(req);
if (!validasiArtikel(data)) { kirimJSON(res, 400, { pesan: "Judul wajib diisi (maksimal 150 karakter) dan kategori wajib diisi (maksimal 50 karakter)." }); return; }
const artikelBaru = { id: idBerikutnya++, judul: data.judul.trim(), kategori: data.kategori.trim() };
artikel.push(artikelBaru);
res.setHeader( "Location", `/api/artikel/${artikelBaru.id}` );
kirimJSON(res, 201, artikelBaru); return; }
if (pathname === "/api/artikel") { res.setHeader("Allow", "GET, POST");
kirimJSON(res, 405, { pesan: "Method tidak diizinkan." }); return; }
kirimJSON(res, 404, { pesan: "Halaman atau endpoint tidak ditemukan." });}
const server = http.createServer((req, res) => { tanganiRequest(req, res).catch((error) => { console.error("Server error:", error.message);
if (res.headersSent) { res.destroy(); return; }
const statusCode = Number.isInteger(error.statusCode) && error.statusCode >= 400 && error.statusCode < 500 ? error.statusCode : 500;
kirimJSON(res, statusCode, { pesan: statusCode === 500 ? "Terjadi kesalahan pada server." : error.message }); });});
server.on("error", (error) => { console.error("Gagal menjalankan server:", error.message);});
server.listen(PORT, HOST, () => { console.log(`Buka http://localhost:${PORT}`);});Server tersebut memiliki dua tanggung jawab.
Pertama, melayani file frontend:
GET /GET /style.cssGET /script.jsKedua, menyediakan endpoint API:
GET /api/artikelPOST /api/artikelUntuk latihan ini, kita sengaja hanya menggunakan operasi membaca dan menambahkan artikel.
Operasi PUT dan DELETE sudah kita pelajari pada artikel sebelumnya.
Catatan keamanan
File yang dilayani server ditentukan melalui daftar route yang eksplisit.
Kita tidak menerima nama file sembarang dari URL untuk langsung dibaca dari sistem.
Pendekatan ini menghindari kebutuhan menangani path traversal pada contoh sederhana.
Namun, server ini tetap hanya ditujukan untuk pembelajaran lokal.
Belum ada autentikasi, penyimpanan permanen, atau perlindungan produksi yang lengkap.
12. Membuat Struktur HTML
Sekarang buka public/index.html.
Masukkan kode berikut:
<!DOCTYPE html><html lang="id"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>Daftar Artikel - BapakGabut</title> <link rel="stylesheet" href="/style.css"> <script src="/script.js" defer></script></head><body> <main class="container"> <header> <h1>Daftar Artikel BapakGabut</h1> <p> Belajar menghubungkan frontend dan backend menggunakan Fetch API. </p> </header>
<section aria-labelledby="judul-daftar"> <div class="judul-bagian"> <h2 id="judul-daftar">Artikel Tersedia</h2> <button id="tombol-refresh" type="button"> Muat Ulang </button> </div>
<p id="status-daftar" role="status" aria-live="polite"></p>
<ul id="daftar-artikel" class="daftar-artikel"></ul> </section>
<section aria-labelledby="judul-form"> <h2 id="judul-form">Tambah Artikel</h2>
<form id="form-artikel"> <label for="judul">Judul Artikel</label> <input id="judul" name="judul" type="text" maxlength="150" required >
<label for="kategori">Kategori</label> <input id="kategori" name="kategori" type="text" maxlength="50" required >
<button id="tombol-simpan" type="submit"> Simpan Artikel </button> </form>
<p id="status-form" role="status" aria-live="polite"></p> </section> </main></body></html>Struktur HTML tersebut mempunyai beberapa elemen penting.
Daftar artikel
Elemen:
<ul id="daftar-artikel"></ul>Akan diisi menggunakan JavaScript setelah data diterima dari API.
Status daftar
Elemen:
<p id="status-daftar" role="status" aria-live="polite"></p>Digunakan untuk menampilkan pesan loading, error, atau informasi lainnya.
Formulir
Formulir mempunyai dua input:
- Judul artikel.
- Kategori.
Ketika formulir dikirim, JavaScript akan mencegah submit HTML biasa dan mengirim request melalui Fetch API.
13. Menambahkan CSS Sederhana
Sekarang buka public/style.css.
Masukkan kode berikut:
* { box-sizing: border-box;}
body { margin: 0; font-family: Arial, sans-serif; line-height: 1.6; background: #f3f4f6; color: #111827;}
.container { width: min(100% - 32px, 760px); margin: 40px auto;}
header,section { background: white; padding: 24px; border-radius: 12px; margin-bottom: 20px;}
h1,h2 { margin-top: 0;}
.judul-bagian { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap;}
.daftar-artikel { list-style: none; padding: 0;}
.daftar-artikel li { padding: 12px; border: 1px solid #d1d5db; border-radius: 8px; margin-bottom: 10px;}
.daftar-artikel strong { display: block;}
label { display: block; margin: 14px 0 6px; font-weight: bold;}
input { display: block; width: 100%; padding: 10px; border: 1px solid #9ca3af; border-radius: 6px; font: inherit;}
button { padding: 10px 16px; border: none; border-radius: 6px; background: #2563eb; color: white; cursor: pointer; font: inherit;}
button:disabled { opacity: 0.6; cursor: not-allowed;}
button:focus-visible,input:focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px;}
#status-daftar,#status-form { min-height: 24px;}
@media (max-width: 480px) { .container { margin: 16px auto; }
header, section { padding: 16px; }}CSS tersebut hanya digunakan untuk membuat halaman lebih nyaman dibaca.
Fokus utama artikel ini tetap pada komunikasi frontend dan backend.
14. Mengambil Data Menggunakan Fetch API
Sekarang kita mulai menulis JavaScript browser.
Buka public/script.js.
Pertama, ambil elemen HTML yang diperlukan.
const daftarArtikel = document.querySelector( "#daftar-artikel");
const statusDaftar = document.querySelector( "#status-daftar");
const tombolRefresh = document.querySelector( "#tombol-refresh");Kemudian buat function untuk mengambil data.
async function ambilArtikel() { statusDaftar.textContent = "Memuat artikel...";
try { const response = await fetch("/api/artikel");
if (!response.ok) { throw new Error( `Gagal mengambil artikel. HTTP ${response.status}` ); }
const data = await response.json();
console.log(data); statusDaftar.textContent = "Artikel berhasil dimuat."; } catch (error) { statusDaftar.textContent = "Gagal memuat artikel. Silakan coba lagi.";
console.error(error); }}Perhatikan URL:
fetch("/api/artikel");Kita tidak perlu menuliskan hostname dan port.
Karena frontend dan backend berjalan pada origin yang sama, browser menggunakan origin halaman saat ini.
Jika halaman dibuka melalui:
http://localhost:3000Maka request menuju:
http://localhost:3000/api/artikel15. Menampilkan Data ke HTML
Mengambil data saja belum cukup.
Kita ingin menampilkannya di halaman.
Buat function:
function tampilkanArtikel(data) { daftarArtikel.replaceChildren();
for (const item of data) { const li = document.createElement("li");
const judul = document.createElement("strong"); judul.textContent = item.judul;
const kategori = document.createElement("span"); kategori.textContent = `Kategori: ${item.kategori}`;
li.append(judul, kategori); daftarArtikel.append(li); }}Function tersebut:
- Mengosongkan daftar artikel sebelumnya.
- Membuat elemen
<li>untuk setiap artikel. - Membuat elemen judul dan kategori.
- Menambahkan elemen ke halaman.
Mengapa menggunakan textContent?
Karena data artikel berasal dari API.
Walaupun backend kita sederhana, data dari sumber eksternal tetap harus diperlakukan dengan hati-hati.
Penggunaan:
judul.textContent = item.judul;Menampilkan nilai sebagai teks.
Ini lebih aman untuk teks biasa daripada memasukkan data yang belum dipercaya langsung ke innerHTML.
16. Menambahkan Loading State
Ketika request sedang berlangsung, pengguna perlu mengetahui bahwa aplikasi sedang bekerja.
Kita dapat menonaktifkan tombol refresh sementara.
Contohnya:
tombolRefresh.disabled = true;Kemudian mengaktifkannya kembali setelah request selesai.
Untuk memastikan proses pemulihan berjalan baik, gunakan finally.
Contohnya:
async function ambilArtikel() { tombolRefresh.disabled = true; statusDaftar.textContent = "Memuat artikel...";
try { const response = await fetch("/api/artikel");
if (!response.ok) { throw new Error( `HTTP ${response.status}` ); }
const data = await response.json();
tampilkanArtikel(data);
statusDaftar.textContent = `${data.length} artikel berhasil dimuat.`; } catch (error) { statusDaftar.textContent = "Gagal memuat artikel. Silakan coba lagi.";
console.error(error); } finally { tombolRefresh.disabled = false; }}Blok finally dijalankan setelah try atau catch selesai, sehingga cocok untuk memulihkan kondisi tombol.
17. Mengirim Data Menggunakan POST
Sekarang kita ingin menambahkan artikel baru melalui formulir.
Untuk mengirim data JSON menggunakan Fetch API, kita dapat menulis:
const response = await fetch("/api/artikel", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ judul: "Belajar Fetch API", kategori: "JavaScript" })});Mari kita pahami.
method
Bagian:
method: "POST"Menentukan HTTP method yang digunakan.
headers
Bagian:
headers: { "Content-Type": "application/json"}Memberi tahu server bahwa body dikirim dalam format JSON.
body
Bagian:
body: JSON.stringify(data)Mengubah object JavaScript menjadi string JSON sebelum dikirim.
Server kemudian membaca body tersebut menggunakan function bacaJSON() yang sudah kita buat.
18. Membaca Pesan Error dari Backend
Backend kita mengembalikan pesan JSON ketika request gagal.
Contohnya:
{ "pesan": "Judul dan kategori wajib diisi."}Kita bisa membaca pesan tersebut agar frontend dapat menampilkan informasi yang lebih berguna.
Contoh:
const response = await fetch("/api/artikel", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data)});
const hasil = await response.json();
if (!response.ok) { throw new Error( hasil.pesan || `HTTP ${response.status}` );}Pada contoh ini, kita membaca body JSON terlebih dahulu.
Kemudian kita memeriksa apakah status HTTP menunjukkan keberhasilan.
Jika tidak, kita menggunakan pesan dari backend.
Untuk aplikasi produksi, pesan dari backend sebaiknya dirancang agar aman ditampilkan kepada pengguna.
Jangan menampilkan stack trace atau detail internal server secara langsung.
19. Membuat JavaScript Frontend Lengkap
Sekarang kita gabungkan seluruh kode frontend.
Ganti isi public/script.js dengan:
const daftarArtikel = document.querySelector( "#daftar-artikel");
const statusDaftar = document.querySelector( "#status-daftar");
const tombolRefresh = document.querySelector( "#tombol-refresh");
const formArtikel = document.querySelector( "#form-artikel");
const tombolSimpan = document.querySelector( "#tombol-simpan");
const statusForm = document.querySelector( "#status-form");
function tampilkanArtikel(data) { daftarArtikel.replaceChildren();
for (const item of data) { const li = document.createElement("li");
const judul = document.createElement("strong"); judul.textContent = item.judul;
const kategori = document.createElement("span"); kategori.textContent = `Kategori: ${item.kategori}`;
li.append(judul, kategori); daftarArtikel.append(li); }}
async function ambilArtikel() { tombolRefresh.disabled = true; statusDaftar.textContent = "Memuat artikel...";
try { const response = await fetch("/api/artikel");
if (!response.ok) { throw new Error( `Gagal mengambil artikel. HTTP ${response.status}` ); }
const data = await response.json();
if (!Array.isArray(data)) { throw new Error("Format data artikel tidak sesuai."); }
tampilkanArtikel(data);
statusDaftar.textContent = `${data.length} artikel berhasil dimuat.`; } catch (error) { statusDaftar.textContent = "Gagal memuat artikel. Silakan coba lagi.";
console.error(error); } finally { tombolRefresh.disabled = false; }}
async function simpanArtikel(event) { event.preventDefault();
const judul = document.querySelector("#judul").value.trim(); const kategori = document.querySelector( "#kategori" ).value.trim();
if (!judul || !kategori) { statusForm.textContent = "Judul dan kategori wajib diisi."; return; }
tombolSimpan.disabled = true; statusForm.textContent = "Menyimpan artikel...";
try { const response = await fetch("/api/artikel", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ judul, kategori }) });
const hasil = await response.json();
if (!response.ok) { throw new Error( hasil.pesan || `HTTP ${response.status}` ); }
statusForm.textContent = `Artikel "${hasil.judul}" berhasil ditambahkan.`;
formArtikel.reset();
await ambilArtikel(); } catch (error) { statusForm.textContent = `Gagal menyimpan artikel: ${error.message}`;
console.error(error); } finally { tombolSimpan.disabled = false; }}
tombolRefresh.addEventListener("click", ambilArtikel);
formArtikel.addEventListener("submit", simpanArtikel);
ambilArtikel();Sekarang halaman kita sudah bisa berkomunikasi dengan backend.
Ketika halaman dibuka, JavaScript otomatis mengambil daftar artikel.
Ketika tombol Muat Ulang ditekan, frontend mengirim request GET lagi.
Ketika formulir dikirim, frontend mengirim request POST.
Jika penyimpanan berhasil, daftar artikel diperbarui tanpa memuat ulang seluruh halaman.
20. Menjalankan Aplikasi
Pastikan struktur folder sesuai:
belajar-fetch-api/├── server.js└── public/ ├── index.html ├── style.css └── script.jsBuka terminal pada folder belajar-fetch-api.
Jalankan:
node server.jsJika berhasil, terminal menampilkan:
Buka http://localhost:3000Kemudian buka browser:
http://localhost:3000Halaman akan menampilkan daftar artikel awal.
Coba isi formulir:
Judul Artikel: Belajar Fetch APIKategori: JavaScriptKemudian tekan tombol Simpan Artikel.
Jika berhasil:
- Frontend mengirim request
POST. - Backend memvalidasi data.
- Backend menambahkan artikel ke array.
- Backend mengembalikan status
201. - Frontend menampilkan pesan keberhasilan.
- Frontend mengambil ulang daftar artikel.
- Artikel baru muncul di halaman.
Kita baru saja menghubungkan frontend dan backend menggunakan JavaScript murni.
21. Melihat Komunikasi API melalui Network
Sekarang buka Developer Tools browser.
Pilih tab Network.
Kemudian muat ulang halaman.
Cari request:
GET /api/artikelPerhatikan informasi berikut:
- Request method.
- Request URL.
- Status code.
- Response headers.
- Response body.
Sekarang tambahkan artikel melalui formulir.
Cari request:
POST /api/artikelPerhatikan request payload yang dikirim.
Contohnya:
{ "judul": "Belajar Fetch API", "kategori": "JavaScript"}Lihat juga respons yang dikembalikan backend.
Jika berhasil, statusnya:
201 CreatedIni membantu kita melihat secara langsung bagaimana browser dan backend berkomunikasi.
22. Mengapa Data Hilang Setelah Server Restart?
Pada latihan ini, backend masih menyimpan artikel dalam array JavaScript.
Contohnya:
let artikel = [ { id: 1, judul: "Belajar HTML", kategori: "HTML" }];Ketika server dihentikan, data yang hanya berada di memori tidak disimpan secara permanen.
Ketika server dijalankan kembali, array dibuat ulang dari nilai awal.
Karena itu, artikel yang ditambahkan melalui formulir akan hilang setelah proses server dimulai ulang.
Ini bukan kesalahan Fetch API.
Fetch API hanya menangani komunikasi HTTP.
Penyimpanan data merupakan tanggung jawab backend dan sistem penyimpanan yang digunakan.
Kita akan membahas penyimpanan permanen pada seri Database.
23. Kesalahan yang Sering Terjadi
Membuka HTML langsung menggunakan file://
Jika kita membuka:
file:///C:/belajar-fetch-api/public/index.htmlHalaman tidak menggunakan origin server HTTP yang kita siapkan.
Akibatnya, request relatif seperti:
fetch("/api/artikel")Tidak akan menuju backend sesuai rancangan latihan.
Buka halaman melalui:
http://localhost:3000Server belum berjalan
Jika Node.js belum dijalankan, browser tidak dapat menghubungi backend.
Pastikan terminal masih menjalankan:
node server.jsPort sudah digunakan
Jika muncul error EADDRINUSE, kemungkinan port 3000 sudah digunakan proses lain.
Hentikan proses sebelumnya atau ubah port server.
Menggunakan hostname atau port yang berbeda
Perhatikan perbedaan antara:
localhostDan:
127.0.0.1Serta perbedaan port.
Untuk latihan ini, selalu buka frontend melalui alamat yang ditampilkan server.
Lupa menggunakan await
Kode:
const response = fetch("/api/artikel");Menghasilkan Promise, bukan objek Response yang sudah tersedia.
Gunakan:
const response = await fetch("/api/artikel");Di dalam async function.
Menganggap fetch gagal otomatis ketika status 404
Tidak.
Periksa:
response.okAtau:
response.statusUntuk mengetahui hasil HTTP request.
Menggunakan innerHTML untuk menampilkan data sembarang
Hindari memasukkan data yang tidak dipercaya langsung ke innerHTML.
Untuk teks biasa, gunakan textContent.
Menganggap validasi frontend sudah cukup
Backend tetap harus memvalidasi data.
Pengguna dapat mengirim request langsung tanpa melalui formulir HTML.
Menampilkan seluruh detail error internal
Frontend sebaiknya menampilkan pesan yang aman dan mudah dipahami.
Detail teknis error internal lebih tepat dicatat pada log backend.
24. Latihan Mandiri
Sekarang kita coba mengembangkan aplikasi.
Latihan 1: Menampilkan jumlah artikel
Tambahkan elemen HTML yang menunjukkan jumlah artikel.
Perbarui nilainya setiap kali data berhasil diambil.
Latihan 2: Menambahkan tombol kosongkan formulir
Tambahkan tombol untuk menghapus seluruh isi input formulir.
Pastikan tombol tersebut tidak mengirim request POST.
Latihan 3: Menampilkan kategori dengan gaya berbeda
Ubah CSS agar kategori artikel terlihat seperti label.
Latihan 4: Menampilkan pesan ketika daftar kosong
Jika API mengembalikan array kosong, tampilkan pesan:
Belum ada artikel.Jangan hanya menampilkan daftar kosong tanpa penjelasan.
Latihan 5: Menguji error jaringan
Jalankan halaman terlebih dahulu.
Kemudian hentikan server Node.js.
Tekan tombol Muat Ulang.
Amati bagaimana frontend menampilkan pesan error.
Setelah itu, jalankan kembali server dan coba lagi.
Latihan 6: Menambahkan tombol hapus
Sebagai tantangan tambahan, kembangkan backend dengan endpoint DELETE seperti pada artikel sebelumnya.
Kemudian tambahkan tombol hapus pada setiap artikel.
Gunakan Fetch API dengan method DELETE.
Pastikan pengguna mendapat konfirmasi sebelum penghapusan dilakukan.
25. Apa yang Sudah Kita Pelajari?
Pada artikel ini, kita berhasil menghubungkan frontend dan backend menggunakan Fetch API.
Kita sudah mempraktikkan:
- Mengirim HTTP request melalui browser.
- Mengenal Promise.
- Menggunakan
.then()dan.catch(). - Menggunakan
async/await. - Menangani error dengan
try...catch. - Memeriksa
response.ok. - Membaca JSON menggunakan
response.json(). - Mengirim JSON menggunakan
JSON.stringify(). - Membuat loading state.
- Menampilkan data API ke HTML.
- Mengirim data formulir menggunakan
POST. - Memahami konsep origin dan CORS.
- Menggunakan satu HTTP server untuk frontend dan API.
Dengan pengetahuan tersebut, kita sudah dapat membuat halaman web yang berkomunikasi dengan backend sendiri.
26. Penutup
Selamat, Gabuters!
Kita berhasil membuat aplikasi sederhana yang menghubungkan HTML, CSS, JavaScript, dan backend Node.js native.
Frontend dapat mengambil data artikel dan mengirim data baru melalui Fetch API.
Backend menerima request, memvalidasi data, dan mengembalikan respons JSON.
Kita juga belajar bahwa komunikasi API membutuhkan penanganan error dan pengalaman pengguna yang baik.
Namun, kita belum selesai.
Pada seri JavaScript sebelumnya, kita sudah membuat halaman profil interaktif.
Bagaimana jika halaman profil tersebut kita kembangkan agar formulir kontaknya benar-benar mengirim data ke backend?
Bagaimana jika backend memvalidasi pesan dan menyediakan endpoint untuk melihat pesan yang diterima?
Pada artikel berikutnya, kita akan membuat Mini Project: Membuat API untuk Halaman Profil Interaktif.
Kita akan menggabungkan seluruh pembelajaran dari seri Backend dan API dalam satu proyek kecil.
Tetap tanpa Express dan tanpa database terlebih dahulu.
Sampai jumpa di artikel berikutnya, Gabuters!
Bagikan Artikel
Buat poster berbagi atau salin tautan artikel ini.
Lanjut membaca
Coba jalur lain
Pilihan konsisten dari artikel lain
Terakhir diperbarui pada , 21 hari yang lalu
Sebagian konten mungkin sudah tidak berlaku