Kenapa perlu backend seperti Supabase?
Silverhawk WebLearn saat ini menyimpan komentar di localStorage browser pengunjung. Artinya:
- Komentar hanya terlihat di perangkat/browser yang sama.
- Kalau user bersihkan cache, komentar hilang.
- Tidak ada data bersama antar pengunjung.
Supabase adalah Backend-as-a-Service (BaaS) open-source berbasis PostgreSQL. Free tier-nya cukup longgar untuk website belajar/komunitas kecil:
- Database Postgres
- Auth (email, Google, dll — opsional)
- Realtime (komentar langsung muncul tanpa refresh)
- Storage file
- API REST & auto-generated
1. Buat akun Supabase (dari nol)
Buka situs Supabase
Kunjungi https://supabase.com → klik Start your project / Sign up.
Daftar
Pilih cara daftar:
- GitHub (paling praktis, karena kamu sudah pakai GitHub Pages)
- atau email + password
Ikuti verifikasi email jika diminta.
Masuk ke Dashboard
Setelah login kamu akan masuk ke Dashboard. Jika belum punya organization, buat saja (nama bebas, misalnya “Silverhawk”).
2. Buat Project baru
New Project
Klik New project. Isi:
- Name:
silverhawk-weblearn(atau apa saja) - Database Password: buat password kuat, simpan di tempat aman (kamu butuh ini nanti jika connect via SQL client)
- Region: pilih yang dekat (Singapore / Tokyo biasanya baik untuk Indonesia)
- Pricing Plan: Free
Klik Create new project. Tunggu 1–2 menit sampai status Active.
3. Buat tabel komentar
Di sidebar kiri, buka Table Editor → New table.
Atau lebih cepat lewat SQL Editor → New query, lalu jalankan:
-- Tabel komentar komunitas
create table public.comments (
id uuid primary key default gen_random_uuid(),
name text not null,
topic text,
message text not null,
avatar_seed text default '1',
created_at timestamptz not null default now()
);
-- Index biar query cepat
create index comments_created_at_idx on public.comments (created_at desc);
-- Aktifkan Realtime (opsional tapi bagus)
alter publication supabase_realtime add table public.comments;
Klik Run. Setelah sukses, cek di Table Editor — tabel comments sudah ada.
Struktur kolom yang dibuat:
| Kolom | Tipe | Keterangan |
|---|---|---|
id | uuid | Primary key otomatis |
name | text | Nama pengirim |
topic | text | Topik (opsional) |
message | text | Isi komentar |
avatar_seed | text | Untuk pravatar.cc |
created_at | timestamptz | Waktu otomatis |
4. Atur Row Level Security (RLS) — penting!
Supabase default mengaktifkan RLS. Tanpa policy yang benar, frontend tidak bisa baca/tulis data.
Di SQL Editor jalankan:
-- Aktifkan RLS
alter table public.comments enable row level security;
-- Siapa saja boleh BACA semua komentar
create policy "Public read comments"
on public.comments for select
to anon, authenticated
using (true);
-- Siapa saja boleh INSERT komentar baru
create policy "Public insert comments"
on public.comments for insert
to anon, authenticated
with check (true);
-- (Opsional) hanya pemilik yang bisa hapus — butuh auth
-- create policy "Users delete own comments" ...
5. Ambil Project URL & Anon Key
Buka Project Settings
Sidebar kiri → ikon gear Project Settings → API.
Salin dua nilai ini
- Project URL — contoh:
https://abcdefghijklmn.supabase.co - anon public key (bukan service_role!) — string panjang yang dimulai dengan
eyJ...
Simpan sementara. Nanti dimasukkan ke file konfigurasi di website.
6. Integrasi ke Silverhawk WebLearn
Ada dua cara: (A) pakai CDN Supabase JS, atau (B) fetch REST API langsung. Di bawah contoh yang paling mudah (CDN).
6.1 Tambah script Supabase di index.html
Sebelum tag </body> (atau sebelum main.js), tambahkan:
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script>
// Ganti dengan nilai dari dashboard Supabase kamu
window.SUPABASE_URL = "https://YOUR_PROJECT_ID.supabase.co";
window.SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....";
</script>
<script src="js/main.js"></script>
6.2 Ganti fungsi komentar di js/main.js
Ganti seluruh fungsi initComments dengan versi yang memakai Supabase (masih ada fallback ke localStorage jika Supabase belum dikonfigurasi):
/* ==========================================================================
Community comments — Supabase + localStorage fallback
========================================================================== */
function initComments(seedComments) {
const list = $("#commentList");
const form = $("#commentForm");
if (!list || !form) return;
const STORAGE_KEY = "shwl-comments";
const hasSupabase = window.supabase && window.SUPABASE_URL && window.SUPABASE_ANON_KEY
&& !window.SUPABASE_URL.includes("YOUR_PROJECT");
let client = null;
if (hasSupabase) {
client = window.supabase.createClient(window.SUPABASE_URL, window.SUPABASE_ANON_KEY);
}
function avatarUrl(seed) {
return `https://i.pravatar.cc/64?img=${seed}`;
}
function formatTime(iso) {
if (!iso) return "baru saja";
const d = new Date(iso);
const now = new Date();
const diff = (now - d) / 1000;
if (diff < 60) return "baru saja";
if (diff < 3600) return Math.floor(diff / 60) + " menit lalu";
if (diff < 86400) return Math.floor(diff / 3600) + " jam lalu";
return d.toLocaleDateString("id-ID", { day: "numeric", month: "short", year: "numeric" });
}
function render(comments) {
list.innerHTML = comments
.map(
(c) => `
<div class="comment">
<img src="${avatarUrl(c.avatar_seed || c.avatarSeed || "1")}" alt="Avatar ${c.name}" loading="lazy" />
<div class="comment-content">
<div class="comment-head"><b>${c.name}</b><span>${formatTime(c.created_at || c.time)}</span></div>
<p>${c.topic ? `<strong>#${c.topic}</strong> — ` : ""}${c.message}</p>
</div>
</div>`
)
.join("");
list.scrollTop = list.scrollHeight;
}
async function loadFromSupabase() {
const { data, error } = await client
.from("comments")
.select("*")
.order("created_at", { ascending: true });
if (error) {
console.warn("[supabase] gagal load komentar", error);
return null;
}
return data || [];
}
async function loadLocal() {
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) || "null");
return stored || (seedComments || []).map((c) => ({ ...c, avatar_seed: c.avatarSeed }));
}
async function bootComments() {
let comments = [];
if (client) {
comments = (await loadFromSupabase()) || (await loadLocal());
} else {
comments = await loadLocal();
}
render(comments);
// Realtime (jika Supabase aktif)
if (client) {
client
.channel("public:comments")
.on("postgres_changes", { event: "INSERT", schema: "public", table: "comments" }, (payload) => {
comments.push(payload.new);
render(comments);
})
.subscribe();
}
}
form.addEventListener("submit", async (e) => {
e.preventDefault();
const name = $("#commentName").value.trim();
const topic = $("#commentTopic").value.trim();
const message = $("#commentMessage").value.trim();
if (!name || !message) return;
const newComment = {
name,
topic: topic || null,
message,
avatar_seed: String(Math.floor(Math.random() * 70) + 1)
};
if (client) {
const { data, error } = await client.from("comments").insert([newComment]).select();
if (error) {
console.error("[supabase] gagal simpan", error);
alert("Gagal mengirim komentar. Coba lagi.");
return;
}
// Realtime akan menangani render, tapi kita bisa push manual juga
if (data && data[0]) {
// sudah di-handle realtime
}
} else {
// Fallback localStorage
const comments = await loadLocal();
comments.push({
...newComment,
time: "baru saja",
avatarSeed: newComment.avatar_seed
});
localStorage.setItem(STORAGE_KEY, JSON.stringify(comments));
render(comments);
}
form.reset();
});
bootComments();
}
7. Deploy ulang ke GitHub Pages
Commit & push
Setelah mengubah index.html dan js/main.js, commit perubahan ke repository GitHub-mu.
git add index.html js/main.js
git commit -m "feat: integrasi komentar Supabase"
git push origin main
Tunggu GitHub Pages update
Biasanya 1–3 menit. Buka website-mu, coba kirim komentar. Cek di Supabase Dashboard → Table Editor apakah data masuk.
Uji realtime
Buka website di dua browser/tab berbeda. Kirim komentar di salah satu — harus muncul otomatis di tab lain tanpa refresh.
Troubleshooting umum
| Masalah | Kemungkinan penyebab | Solusi |
|---|---|---|
| Komentar tidak muncul | RLS policy belum dibuat | Jalankan SQL policy di langkah 4 |
| Error CORS / network | URL/key salah | Cek Project URL & anon key |
| Insert gagal | Policy insert belum ada | Buat policy “Public insert comments” |
| Realtime tidak jalan | Belum alter publication | Jalankan SQL realtime di langkah 3 |
| Key bocor di GitHub | Anon key memang public by design | Aman selama RLS ketat. Jangan push service_role |
Alternatif gratis selain Supabase
Berikut 3 alternatif yang juga gratis dan cocok untuk website statis di GitHub Pages:
1. Firebase (Google)
Firestore Auth Realtime
Backend milik Google. Free tier (Spark plan) sangat longgar untuk traffic kecil-menengah.
- Firestore = database dokumen NoSQL
- Realtime updates built-in
- Auth email/Google mudah
- Dokumentasi & tutorial sangat banyak
Cocok jika: kamu sudah akrab ekosistem Google / butuh auth cepat.
Mulai: firebase.google.com
2. Appwrite
Open source Cloud & Self-host
Alternatif open-source yang mirip Supabase. Ada Cloud free tier dan bisa self-host.
- Database, Auth, Storage, Functions
- Realtime via WebSocket
- SDK JS ringan
- Bisa deploy ke Cloud Appwrite gratis
Cocok jika: ingin open-source + kontrol lebih besar.
Mulai: appwrite.io
3. PocketBase
Single binary SQLite Self-host
Backend sangat ringan (satu file binary). Database SQLite + realtime + auth.
- Sangat mudah di-host (Railway, Render, Fly.io free tier, VPS murah)
- Admin UI built-in
- Realtime & file storage
- Cocok untuk project kecil–menengah
Cocok jika: ingin self-host sederhana & hemat resource.
Mulai: pocketbase.io
• Ingin paling mirip Postgres + setup cepat → Supabase
• Ingin ekosistem Google & tutorial berlimpah → Firebase
• Ingin open-source cloud → Appwrite
• Ingin self-host super ringan → PocketBase
Langkah selanjutnya (opsional)
- Tambahkan Auth (login Google/GitHub) supaya komentar bisa di-moderasi.
- Buat tabel
newsletteruntuk menyimpan email subscriber. - Pakai Supabase Storage untuk upload gambar artikel.
- Tambahkan rate limiting / CAPTCHA (Cloudflare Turnstile gratis) agar tidak di-spam.