Intro to Supabase og JavaScript
Lag en enkel nettside som lagrer og viser meldinger via Supabase.Trinn 1 — Opprett database med én tabell
- 1.1Gå til Supabase → opprett et prosjekt.
- 1.2Lag tabellen
messagesmed kolonner:id(PK, identity),content(text),created_at(timestamptz defaultnow()).
-- Opprett tabell (hvis den ikke finnes)
create table if not exists public.messages (
id bigint generated always as identity primary key,
content text not null,
created_at timestamptz not null default now()
);
-- Aktiver radnivå-sikkerhet
alter table public.messages enable row level security;
-- Tillat at alle (anon) kan lese meldinger
create policy "Anon kan lese"
on public.messages
for select
using (true);
-- Tillat at alle (anon) kan skrive nye meldinger
create policy "Anon kan skrive"
on public.messages
for insert
with check (true);
-- (valgfritt) sørg for at public kan bruke tabellen
grant all on public.messages to anon;
Trinn 2 — Finn nøkler
Viktig: Hent API URL og Anon key fra
Supabase → Settings → API.
Husk: API URL og Anon key skal byttes inn i
koden under. Se etter const SUPABASE_ANON_KEY og const SUPABASE_URL. Lim inn mellom
"...".
Trinn 3 — Lag elevside (index.html)
Kopier hele blokken under til en ny fil som heter index.html.
<!doctype html>
<html lang="no">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Supabase demo — Meldinger</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background:#f8fafc; }
.card-elev { border:1px solid #e9ecef; border-radius:1rem; }
.muted { color:#6c757d; }
</style>
</head>
<body>
<nav class="navbar navbar-expand-md bg-white border-bottom">
<div class="container">
<a class="navbar-brand fw-semibold" href="#">Supabase demo</a>
</div>
</nav>
<main class="container my-4">
<header class="text-center mb-4">
<h1 class="h4 fw-bold">Navn + kort melding</h1>
<p class="text-muted mb-0">Skriver til databasen og viser under.</p>
</header>
<section class="row g-4">
<div class="col-12 col-lg-5">
<div class="card card-elev shadow-sm">
<div class="card-body">
<h2 class="h6 mb-3">Send inn</h2>
<form id="form" class="needs-validation" novalidate>
<div class="mb-3">
<label class="form-label" for="name">Navn</label>
<input id="name" class="form-control" minlength="2" maxlength="30" required placeholder="F.eks. Sara">
<div class="invalid-feedback">Skriv inn navnet ditt (2–30 tegn).</div>
</div>
<div class="mb-3">
<label class="form-label" for="content">Kort melding</label>
<input id="content" class="form-control" maxlength="140" required placeholder="Hei verden! (maks 140 tegn)">
<div class="invalid-feedback">Skriv en kort melding (maks 140 tegn).</div>
</div>
<button class="btn btn-primary w-100" type="submit">Lagre</button>
<div id="status" class="form-text mt-2 muted">Klar</div>
</form>
</div>
</div>
</div>
<div class="col-12 col-lg-7">
<div class="card card-elev shadow-sm">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-2">
<h2 class="h6 mb-0">Meldinger</h2>
<span class="badge text-bg-light"><span id="count">0</span> stk</span>
</div>
<div id="msgs" class="d-grid gap-2"></div>
</div>
</div>
</div>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script>
const SUPABASE_URL = "https://DIN-PROSJEKT-ID.supabase.co";
const SUPABASE_ANON_KEY = "ey...DIN_ANON_KEY...";
const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const form = document.getElementById('form');
const nameInput = document.getElementById('name');
const contentInput = document.getElementById('content');
const msgsEl = document.getElementById('msgs');
const statusEl = document.getElementById('status');
const countEl = document.getElementById('count');
function esc(s) {
return (s || '').replace(/[<>&"']/g, c => ({
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[c]));
}
function parseRow(r) {
const raw = r.content || '';
const i = raw.indexOf('::');
if (i === -1) return { name: 'Anonym', message: raw, created_at: r.created_at };
return { name: raw.slice(0,i), message: raw.slice(i+2), created_at: r.created_at };
}
async function loadMessages() {
statusEl.textContent = 'Laster...';
const { data, error } = await supabase.from('messages').select('*').order('created_at', { ascending:false }).limit(100);
if (error) { statusEl.textContent = 'Feil: ' + error.message; return; }
const rows = data || [];
countEl.textContent = rows.length;
msgsEl.innerHTML = rows.map(r => {
const it = parseRow(r);
const when = new Date(it.created_at).toLocaleString();
return `<div class="border rounded p-2"><strong>${esc(it.name)}:</strong> ${esc(it.message)} <span class="ms-2 muted">${when}</span></div>`;
}).join('');
statusEl.textContent = 'Klar';
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
form.classList.add('was-validated');
if (!form.checkValidity()) return;
const name = nameInput.value.trim();
const msg = contentInput.value.trim();
if (!name || !msg) return;
const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.disabled = true;
statusEl.textContent = 'Lagrer...';
const { error } = await supabase.from('messages').insert({ content: name + '::' + msg });
submitBtn.disabled = false;
if (error) { statusEl.textContent = 'Feil ved lagring: ' + error.message; return; }
form.reset();
form.classList.remove('was-validated');
statusEl.textContent = 'Lagret!';
nameInput.focus();
await loadMessages();
});
loadMessages();
</script>
</body>
</html>