Modul Belajar
Membangun Aplikasi CRUD Armada dari Nol
Panduan langkah demi langkah untuk memasang, menjalankan, dan mempublikasikan aplikasi Sukarobot — sebuah CRUD sederhana berbasis React + Vite di sisi tampilan dan Express + MySQL di sisi data.
Alur data saat mode pengembangan.
Apa yang akan kita bangun?
Sukarobot adalah aplikasi pengelolaan data armada robot. Satu halaman berisi daftar armada, kolom pencarian, formulir tambah/ubah, dan tombol hapus dengan konfirmasi. Semua data disimpan di MySQL melalui API.
Teknologi yang dipakai
- Backend: Node.js, Express 5, mysql2 — API JSON di port 5000.
- Frontend: React 18 + Vite — halaman di port 5173.
- Database: MySQL, database
sukarobot, tabelarmada. - Styling: satu file CSS biasa, tanpa framework.
Ikuti urutannya dari atas ke bawah. Setiap bagian punya langkah bernomor dan perintah terminal yang bisa langsung disalin. Jangan lewati bagian Prasyarat dan Database — dua bagian itu paling sering jadi sumber error.
Yang perlu disiapkan
Ada tiga hal yang harus terpasang di komputer: Node.js, MySQL, dan terminal. Tidak perlu akun atau layanan berbayar untuk belajar di komputer sendiri.
Node.js 18 atau lebih baru
Unduh installer dari nodejs.org, pilih versi LTS, lalu install dengan opsi bawaan. Setelah selesai, buka terminal baru dan cek versinya:
node -v
npm -v
Angka pertama harus 18 atau lebih (contoh: v20.11.0). Kalau perintah tidak dikenali, tutup terminal lalu buka lagi agar PATH diperbarui.
MySQL
Pilih salah satu: MySQL Community Server, MariaDB, atau Laragon untuk pengguna Windows. Catat host, user, dan password yang kamu set saat instalasi — nanti dipakai di bagian Database.
MySQL harus sedang berjalan setiap kali backend dinyalakan. Kalau service MySQL mati, backend akan langsung berhenti dengan pesan gagal terhubung ke mysql.
Terminal / command prompt
Windows: Git Bash atau PowerShell. macOS/Linux: Terminal bawaan. Contoh perintah di modul ini memakai format bash; untuk PowerShell, cara mengatur environment variable ditulis di bagian Backend.
Editor kode
VS Code atau editor lain. Tidak wajib, tapi sangat membantu untuk membaca dan mengubah file.
Memahami isi project
Project terdiri dari dua aplikasi terpisah. Keduanya punya package.json
sendiri, jadi npm install dijalankan dua kali — sekali di backend, sekali di frontend.
sukarobot/
├─ backend/
│ ├─ db.js koneksi + pembuatan database/tabel
│ ├─ server.js route API Express
│ └─ package.json
├─ frontend/
│ ├─ index.html
│ ├─ vite.config.js port 5173 + proxy /api
│ ├─ package.json
│ └─ src/
│ ├─ App.jsx halaman utama
│ ├─ api.js pemanggilan API
│ ├─ main.jsx entry point React
│ ├─ styles.css
│ └─ components/
│ ├─ armadaList.jsx
│ └─ armadaForm.jsx
├─ index.php modul belajar ini
└─ README.md
Siapa melakukan apa?
backend/server.js— menerima request HTTP, memvalidasi, lalu menjalankan query.backend/db.js— membuat database dan tabel sekali saat start, lalu menyediakan fungsiquery().frontend/src/App.jsx— menyimpan state daftar armada, pencarian, dan form.frontend/src/api.js— satu tempat untuk semuafetchke/api/armada.
Karena frontend memanggil /api (path relatif), semua route backend harus diawali /api. Kalau salah satu diubah, ubah keduanya: server.js dan src/api.js.
Menyiapkan database
Kabar baiknya: kamu tidak perlu membuat database secara manual. Saat backend pertama kali
dijalankan, db.js otomatis membuat database sukarobot dan tabel
armada bila belum ada.
-
Pastikan MySQL menyala
Lewat Laragon klik Start All, atau jalankan service MySQL di sistem operasi.
-
Periksa kredensial di
backend/db.jsDefault: host
localhost, userroot, password kosong, databasesukarobot. Sesuaikan bila MySQL-mu memakai password. -
Jalankan backend sekali
Perintahnya ada di bagian Backend. Kalau berhasil, database dan tabel langsung terbentuk.
Pengaturan koneksi
| Variable | Default | Keterangan |
|---|---|---|
DB_HOST | localhost | Lokasi server MySQL |
DB_USER | root | User database |
DB_PASSWORD | kosong | Password user database |
DB_NAME | sukarobot | Nama database |
PORT | 5000 | Port server API |
Nilai default bisa diganti tanpa mengedit file, cukup lewat environment variable sebelum perintah start:
# Git Bash, macOS, Linux
DB_PASSWORD=rahasia npm run dev
# PowerShell (Windows)
$env:DB_PASSWORD="rahasia"; npm run dev
# Command Prompt (Windows)
set DB_PASSWORD=rahasia && npm run dev
Skema tabel
Tabel armada memiliki lima kolom. Waktu dibuat otomatis oleh db.js:
| Kolom | Tipe | Keterangan |
|---|---|---|
id | INT AUTO_INCREMENT | Primary key |
nama | VARCHAR(255) | Nama armada, wajib |
tipe | VARCHAR(255) | Tipe armada, wajib |
deskripsi | VARCHAR(255) | Keterangan singkat, wajib |
created_at | TIMESTAMP | Terisi otomatis saat data dibuat |
updated_at | TIMESTAMP | Diperbarui otomatis saat data diubah |
Perintah CREATE TABLE IF NOT EXISTS tidak mengubah tabel yang sudah ada. Setelah mengedit struktur di db.js, hapus dulu tabel armada (atau jalankan ALTER TABLE manual), baru start ulang backend.
Menjalankan server API
Backend adalah sumber data. Nyalakan lebih dulu sebelum frontend, karena halaman React langsung meminta daftar armada saat dibuka.
-
Masuk ke folder backend
terminal
cd backend -
Pasang dependency
Hanya perlu sekali, atau setiap kali
package.jsonberubah.terminalnpm install -
Jalankan server
terminal
npm run devJika berhasil muncul:
api berjalan di http://localhost:5000.
npm run dev menjalankan node --watch server.js: setiap kali file backend disimpan, server restart otomatis. Untuk mode biasa tanpa watch, gunakan npm start.
Cek backend tanpa frontend
Buka terminal baru (biarkan server tetap berjalan), lalu uji dengan curl:
# Ambil semua armada (awalnya array kosong)
curl http://localhost:5000/api/armada
# Tambah satu armada
curl -X POST http://localhost:5000/api/armada \
-H "Content-Type: application/json" \
-d '{"nama":"Robot Alpha","tipe":"Otonom","deskripsi":"Robot pengantar barang"}'
Respons sukses berisi objek dengan id dan timestamp. Kalau muncul {"error":"..."}, baca pesannya — biasanya karena kolom kosong atau MySQL tidak siap.
Kalau port 5000 sudah dipakai
PORT=5001 npm run dev
Setelah itu sesuaikan juga proxy di frontend/vite.config.js ke http://localhost:5001, jika tidak frontend akan tetap mencari port 5000.
Menjalankan tampilan React
Frontend berjalan di port 5173 dan meneruskan semua request /api ke backend
di port 5000. Inilah yang membuat frontend tidak perlu menulis alamat lengkap di setiap
pemanggilan.
-
Buka terminal baru
Jangan tutup terminal backend. Modul ini butuh dua terminal berjalan bersamaan.
-
Masuk ke folder frontend dan pasang dependency
terminal
cd frontend npm install -
Jalankan Vite
terminal
npm run devVite menampilkan alamat
http://localhost:5173. Buka di browser.
Di vite.config.js terdapat pengaturan proxy: { '/api': 'http://localhost:5000' }. Artinya request ke http://localhost:5173/api/armada diteruskan ke backend tanpa mengubah path. Ini hanya berlaku saat npm run dev.
Perintah frontend lainnya
| Perintah | Fungsi |
|---|---|
npm run dev | Server pengembangan dengan hot reload |
npm run build | Membuat versi produksi di folder dist/ |
npm run preview | Melihat hasil build secara lokal |
Uji semua fitur
Dengan dua terminal aktif — backend di 5000 dan frontend di 5173 — buka http://localhost:5173 dan lakukan lima pemeriksaan berikut.
- Halaman menampilkan judul Daftar Armada dan tombol Tambah Armada.
- Klik Tambah Armada, isi ketiga kolom, lalu klik Simpan. Kartu baru muncul dan pesan sukses tampil.
- Ketik kata kunci di kolom pencarian. Daftar menyempit sesuai nama, tipe, atau deskripsi.
- Klik Ubah pada satu kartu, ganti namanya, lalu simpan. Perubahan langsung terlihat.
- Klik Hapus, setujui konfirmasi, dan kartu tersebut hilang dari daftar.
Alur data satu kali simpan
- Form mengirim data ke
api.create(). api.jsmemanggilPOST /api/armada.- Vite meneruskan request ke Express di port 5000.
server.jsmemvalidasi, menyimpan ke MySQL, dan mengembalikan baris baru.App.jsxmenaikkan nilaiversisehingga daftar dimuat ulang.
Kalau daftar kosong terus dan muncul pesan gagal memuat, buka http://localhost:5000/api/armada di browser. Jika alamat itu tidak merespons, masalahnya di backend atau MySQL — bukan di React.
Kontrak data
Semua endpoint berada di bawah /api/armada. Format request dan respons selalu JSON.
| Method | Path | Fungsi |
|---|---|---|
| GET | /api/armada?q= | Daftar armada, opsional difilter kata kunci |
| GET | /api/armada/:id | Ambil satu armada |
| POST | /api/armada | Tambah armada |
| PUT | /api/armada/:id | Ubah armada |
| DELETE | /api/armada/:id | Hapus armada |
Body request
{
"nama": "Robot Alpha",
"tipe": "Otonom",
"deskripsi": "Robot pengantar barang"
}
Contoh respons
{
"id": 1,
"nama": "Robot Alpha",
"tipe": "Otonom",
"deskripsi": "Robot pengantar barang",
"created_at": "2026-11-10 22:47:44",
"updated_at": "2026-11-10 22:47:44"
}
Format error
| Status | Respons | Penyebab |
|---|---|---|
400 | {"error":"Nama tidak boleh kosong"} | Ada kolom wajib yang kosong |
400 | {"error":"Data sudah ada"} | Pelanggaran unique key (ER_DUP_ENTRY) |
404 | {"error":"Data tidak ditemukan"} | ID tidak ada di tabel |
500 | {"error":"Terjadi kesalahan pada server"} | Query gagal; detailnya dicetak di terminal backend |
DELETE yang berhasil mengembalikan {"message":"Data berhasil dihapus"}.
Bagian penting yang perlu dipahami
Kamu tidak perlu menghafal semua baris. Fokus pada empat file ini: koneksi database, route API, pemanggil API di frontend, dan pengelolaan state halaman.
1. Koneksi database — backend/db.js
Koneksi memakai mysql2/promise agar bisa dipakai dengan async/await.
Fungsi init() membuat database lebih dulu dengan koneksi tanpa nama database,
baru setelah itu membuat pool dan tabel.
const mysql = require('mysql2/promise');
const config = {
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'sukarobot'
};
async function init() {
const awal = await mysql.createConnection({
host: config.host,
user: config.user,
password: config.password
});
await awal.query(`CREATE DATABASE IF NOT EXISTS \`${config.database}\``);
await awal.end();
pool = mysql.createPool({ ...config, dateStrings: true, connectionLimit: 10 });
await pool.query(`CREATE TABLE IF NOT EXISTS armada (...)`);
}
require('mysql2') memakai callback, sehingga await akan melempar error. Selalu gunakan require('mysql2/promise') untuk gaya async/await.
2. Route API — backend/server.js
Setiap route dibungkus try/catch dan memakai prepared statement
(tanda ?) supaya nilai dari pengguna tidak disisipkan langsung ke SQL.
app.get('/api/armada', async (req, res) => {
try {
const q = String(req.query.q ?? '').trim();
const [rows] = await db.query(
'SELECT * FROM armada WHERE nama LIKE ? OR tipe LIKE ? OR deskripsi LIKE ?',
[`%${q}%`, `%${q}%`, `%${q}%`]
);
res.json(rows);
} catch (err) {
tanganiError(err, res);
}
});
app.post('/api/armada', async (req, res) => {
const { data, error } = validasi(req.body);
if (error) return res.status(400).json({ error });
const [hasil] = await db.query(
'INSERT INTO armada (nama, tipe, deskripsi) VALUES (?, ?, ?)',
[data.nama, data.tipe, data.deskripsi]
);
res.status(201).json(await ambilSatu(hasil.insertId));
});
3. Pemanggil API — frontend/src/api.js
Semua fetch dikumpulkan di satu objek. Fungsi request() menangani
header JSON, membaca respons, dan melempar error bila status bukan 2xx.
const BASE = '/api/armada';
async function request(url, options = {}) {
const res = await fetch(url, {
headers: { 'Content-Type': 'application/json' },
...options,
});
const body = res.status === 204 ? null : await res.json();
if (!res.ok) throw new Error(body?.error || 'Terjadi kesalahan.');
return body;
}
export const api = {
list: (q = '') => request(`${BASE}?q=${encodeURIComponent(q)}`),
create: (data) => request(BASE, { method: 'POST', body: JSON.stringify(data) }),
update: (id, data) => request(`${BASE}/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
remove: (id) => request(`${BASE}/${id}`, { method: 'DELETE' }),
};
4. State halaman — frontend/src/App.jsx
Daftar dimuat ulang setiap kali q (kata pencarian) atau versi
(penanda data berubah) ikut berubah. Setelah simpan atau hapus, cukup naikkan
versi — tidak perlu mengubah array secara manual.
const [armadas, setArmadas] = useState([]);
const [q, setQ] = useState('');
const [versi, setVersi] = useState(0);
useEffect(() => {
let aktif = true;
async function muat() {
const data = await api.list(q);
if (aktif) setArmadas(data);
}
muat();
return () => { aktif = false; };
}, [q, versi]);
// setelah simpan / hapus:
setVersi((v) => v + 1);
Penanda aktif mencegah respons lama menimpa hasil pencarian yang lebih baru saat pengguna mengetik cepat.
Menaikkan ke domain sendiri
Ada dua hal berbeda yang dipublikasikan: halaman modul ini
(index.php) dan aplikasinya (frontend + backend + MySQL).
Keduanya bisa berada di domain yang sama, tetapi cara menyiapkannya berbeda.
Langkah 1 — build frontend
cd frontend
npm run build
Perintah ini menghasilkan folder frontend/dist berisi index.html dan aset statis. Folder itulah yang diunggah ke hosting.
Langkah 2 — sesuaikan alamat API
Saat mode pengembangan, /api diteruskan oleh Vite. Di produksi tidak ada Vite,
jadi ada dua pilihan:
- Satu domain dengan reverse proxy. Arahkan
/api/*ke server Node, misalnya lewat Nginx. Frontend tetap memakaiBASE = '/api/armada'. - Domain API terpisah. Ubah
BASEdisrc/api.jsmenjadi alamat lengkap, contohhttps://api.domainmu.com/api/armada, lalu build ulang. Backend sudah mengaktifkan CORS, sehingga request lintas domain diizinkan.
// opsi 2: backend di subdomain terpisah
const BASE = 'https://api.domainmu.com/api/armada';
Langkah 3 — siapkan hosting backend
Backend butuh runtime Node.js dan koneksi ke MySQL. Ini tidak bisa dijalankan di hosting PHP biasa. Pilihan yang umum:
| Jenis hosting | Cocok untuk | Catatan |
|---|---|---|
| VPS (Ubuntu) | Belajar deployment penuh | Install Node, MySQL, Nginx, lalu jalankan dengan PM2 |
| Platform Node (Railway, Render, dsb.) | Cepat online | Set env DB_* di dashboard; sediakan MySQL terkelola |
| cPanel dengan fitur Node.js | Hosting yang mendukung | Cek dulu tersedia “Setup Node.js App” dan MySQL remote |
index.php hanya untuk halaman dokumentasi. Hosting yang hanya mendukung PHP tidak bisa menjalankan server.js atau MySQL aplikasi. Frontend boleh diunggah sebagai file statis, tetapi API tetap harus berjalan di server Node.
Langkah 4 — unggah dokumentasi
Unggah index.php ke folder publik domain (misalnya public_html/modul/index.php).
File ini mandiri — tidak butuh database, build, atau file lain. Buka
https://domainmu.com/modul/ dan modul siap dibaca.
Checklist produksi
- Password MySQL tidak lagi kosong; simpan lewat environment variable di server.
BASEdisrc/api.jssudah menunjuk alamat API produksi (atau reverse proxy dikonfigurasi).- Frontend sudah di-build ulang setelah mengubah
BASE. - Backend berjalan permanen (PM2, systemd, atau proses platform), bukan hanya
npm run dev. - Coba semua endpoint dari browser/Postman setelah online.
Error yang sering muncul
Sebagian besar masalah berasal dari tiga hal: MySQL belum siap, backend belum jalan, atau alamat API tidak cocok. Buka bagian yang sesuai.
Backend berhenti dengan “gagal terhubung ke mysql”
MySQL tidak menerima koneksi. Nyalakan service MySQL (Laragon: Start All), lalu jalankan ulang backend. Pastikan host di db.js benar.
“Access denied for user 'root'@'localhost'”
Password di konfigurasi tidak cocok. Set DB_PASSWORD sesuai password MySQL-mu, atau kosongkan bila memang tanpa password. Untuk PowerShell: $env:DB_PASSWORD="rahasia"; npm run dev.
“EADDRINUSE: address already in use :::5000”
Port 5000 dipakai proses lain. Hentikan proses tersebut, atau jalankan PORT=5001 npm run dev lalu sesuaikan proxy di vite.config.js.
Frontend menampilkan “Gagal memuat data armada”
Buka http://localhost:5000/api/armada langsung di browser. Kalau gagal, backend bermasalah. Kalau berhasil, periksa proxy di vite.config.js dan pastikan BASE di src/api.js adalah /api/armada.
Halaman blank / layar putih
Buka konsol browser (F12). Error biasanya berupa import file yang tidak ada atau typo pada nama komponen. Pastikan semua file di src/ benar-benar ada, lalu simpan ulang.
“Cannot find module ...” saat npm run dev
Dependency belum terpasang atau terminal berada di folder yang salah. Jalankan npm install di folder yang memiliki package.json sesuai aplikasi yang dijalankan.
Data tidak muncul setelah menambah kolom di db.js
CREATE TABLE IF NOT EXISTS tidak mengubah tabel lama. Hapus tabel armada lewat phpMyAdmin, atau jalankan ALTER TABLE armada ADD COLUMN ..., lalu restart backend.
API berhasil di localhost tetapi gagal setelah online
Biasanya BASE masih relatif (/api/armada) padahal backend ada di domain lain, atau server Node tidak berjalan. Periksa tab Network di DevTools: lihat URL yang dipanggil dan status responsnya.
Setelah semuanya berjalan
Cara terbaik memastikan paham adalah membangun ulang. Mulai dari project kosong, lalu bandingkan hasilnya dengan kode ini.
-
Bangun ulang dari nol
Buat folder
backenddanfrontend, lalu tulis ulang file satu per satu tanpa menyalin. Fokus pada alur request, bukan pada hafalan. -
Tambah satu kolom baru
Misalnya
statusdengan nilai Aktif atau Perbaikan. Ubah tabel, validasi, route, form, dan tampilan kartu. Ini melatih pemahaman dari database sampai UI. -
Tambahkan pagination
Tambahkan
?page=dan?limit=di backend, lalu tombol “Sebelumnya / Berikutnya” di frontend. -
Rapikan pesan error
Buat pemberitahuan yang lebih ramah untuk jaringan putus, alih-alih hanya teks error dari API.
-
Publikasikan
Ikuti bagian Build & publikasi. Menghadapi error deployment adalah bagian dari belajar.
Kalau semua checklist di bagian sebelumnya tercentang, kamu sudah punya aplikasi CRUD lengkap: database, API, antarmuka, dan dokumentasi. Selamat membangun.