12 KiB
Les 8 — Docenttekst
Van In-Memory naar Supabase
Lesoverzicht
| Gegeven | Details |
|---|---|
| Les | 8 van 18 |
| Onderwerp | Supabase koppelen aan Next.js |
| Duur | 3 uur (09:00 – 12:00) |
| Voorbereiding | Werkend QuickPoll project, Supabase project met polls/options tabellen |
| Benodigdheden | Laptop, Cursor/VS Code, browser, Supabase account |
| Aanpak | Deel 1-3 klassikaal (docent loopt PDF door met studenten). Deel 4 zelfstandig. |
Leerdoelen
Na deze les kunnen studenten:
- De Supabase JavaScript client installeren en configureren
- Environment variables gebruiken voor API keys
- Data ophalen via Supabase queries (select met relaties, eq, single)
- Het verschil uitleggen tussen Server en Client Components
- Een formulier bouwen dat data INSERT in Supabase
Lesplanning
09:00–09:15 | Welkom & Intro (15 min)
📌 Slide 1, 2, 3
Doel: Studenten welkom heten, plan uitleggen, en PDF uitdelen.
Wat te zeggen:
- "Vorige week hebben we een werkend polling app gebouwd met in-memory data."
- "Vandaag koppelen we Supabase: onze database-as-a-service."
- "Jullie krijgen een PDF met de volledige lesopdracht. We lopen Deel 1 t/m 3 samen door. Deel 4 doen jullie zelfstandig."
- "In de PDF staan grijze blokken die je kunt copy-pasten, en TODO-blokken die je zelf moet invullen."
Check vooraf:
- Iedereen heeft Supabase account met polls en options tabellen
- Iedereen heeft QuickPoll project lokaal runnen op localhost:3000
- Deel de Lesopdracht PDF uit (digitaal)
09:15–09:45 | KLASSIKAAL: PDF Deel 1 — Setup (30 min)
📌 Slide 4 + PDF Deel 1
Doel: Samen de setup doorlopen. Iedereen heeft aan het einde: supabase client geïnstalleerd, .env.local, supabase.ts, en types.
Toon Slide 4 — Van Array naar Database. Leg uit:
- "Tot nu toe sloegen we data op in een array. Dat verdwijnt bij restart."
- "Supabase geeft ons een echte PostgreSQL database in de cloud."
- Toon links de oude array, rechts de database structuur.
Open de PDF bij Deel 1 en loop stap voor stap door:
Stap 1.1 — npm install
- "Open je terminal, voer uit:
npm install @supabase/supabase-js" - Wacht tot iedereen klaar is
- "Herstart je dev server!"
Stap 1.2 — .env.local
- "Open Supabase dashboard → Settings → API"
- "Kopieer je Project URL en anon key"
- "Maak
.env.localaan in de root van je project" - Laat ze het invullen, loop rond en check
- Let op: herstart dev server na aanmaken .env.local!
Stap 1.3 — lib/supabase.ts
- "Dit is onze client — zo praat je app met Supabase"
- Laat ze de code copy-pasten uit de PDF
- Leg uit:
createClientmaakt de verbinding,process.env.NEXT_PUBLIC_...leest de env vars
Stap 1.4 — types/index.ts
- "Dit zijn de TypeScript types die matchen met onze database tabellen"
- Laat ze copy-pasten
- Wijs op:
id: number,options: Option[](de relatie)
Stap 1.5 — vote_option SQL functie aanmaken
- "Voordat we kunnen stemmen, hebben we een PostgreSQL functie nodig in Supabase."
- "Open Supabase dashboard → SQL Editor"
- Laat ze deze SQL uitvoeren (staat in PDF):
create or replace function public.vote_option(option_id bigint) returns void language sql security definer as $$ update public.options set votes = votes + 1 where id = option_id; $$; - Leg uit: "Dit is een database functie. We roepen 'm straks aan met
supabase.rpc('vote_option', { option_id }). Een RPC = Remote Procedure Call — je voert PostgreSQL code uit vanuit je app." - Waarom? "We hadden ook een gewone UPDATE kunnen doen, maar met een functie hou je de logica in de database. Volgende les met Auth gaan we deze functie uitbreiden."
- Let op: "Zonder deze functie krijg je later een PGRST202 error bij het stemmen — 'Could not find the function public.vote_option'."
Check: "Heeft iedereen de setup af? Geen errors? Handen omhoog als je klaar bent."
09:45–10:00 | KLASSIKAAL: PDF Deel 2 — Supabase Queries (15 min)
📌 Slide 5 + PDF Deel 2
Doel: Studenten begrijpen de queries en vullen de TODO-blokken in lib/data.ts in.
Toon Slide 5 — Supabase Queries. Leg de vier operaties uit:
- SELECT alles:
.from("polls").select("*, options(*)")— de*haalt alles op,options(*)volgt de relatie - SELECT een:
.eq("id", 5).single()— filter + verwacht 1 resultaat - INSERT:
.insert({ question }).select().single()— maak nieuw record, krijg het terug - RPC:
.rpc("vote_option", { option_id })— roep een database function aan
Tip: Schrijf deze vier queries op het whiteboard. Studenten kijken hier de rest van de les naar.
Open de PDF bij Deel 2 — Stap 2.1:
- "Vervang de inhoud van
lib/data.ts. De imports staan er al." - "Nu de TODO-blokken. Laten we de eerste samen doen."
getPolls() — doe samen voor:
const { data, error } = await supabase
.from("polls")
.select("*, options(*)");
if (error) {
console.error(error);
return [];
}
return data || [];
- "Zie je?
.from("polls")kiest de tabel,.select("*, options(*)")haalt alles op inclusief de relatie."
getPollById() — laat ze zelf proberen (2 min), loop dan door:
const { data, error } = await supabase
.from("polls")
.select("*, options(*)")
.eq("id", id)
.single();
if (error) return null;
return data;
- "
.eq("id", id)filtert op 1 specifieke poll..single()zegt: ik verwacht 1 resultaat."
votePoll() — laat ze zelf proberen (2 min), loop dan door:
const { error } = await supabase
.rpc("vote_option", { option_id: optionId });
if (error) {
console.error(error);
return false;
}
return true;
- "
.rpc()roept een PostgreSQL function aan die we eerder hebben aangemaakt."
Check: "Heeft iedereen alle drie de functies ingevuld?"
10:00–10:15 | KLASSIKAAL: PDF Deel 3 — Componenten (15 min)
📌 Slide 6 + PDF Deel 3
Doel: Studenten copy-pasten de vier componenten uit de PDF en testen de app.
Toon Slide 6 — Server vs Client. Leg kort uit:
- "Server Components: async, kunnen
await getPolls()doen. Draaien op de server." - "Client Components:
'use client'bovenaan, kunnenuseState/onClickgebruiken. Draaien in de browser." - "Vuistregel: Server haalt data, Client maakt het interactief."
Open de PDF bij Deel 3 en loop door:
Stap 3.1 — app/page.tsx
- "Dit is de homepage. Let op:
async functionenawait getPolls()— dit is een Server Component." - "Copy-paste uit de PDF."
- "De
<Link href="/create">link werkt straks na Deel 4."
Stap 3.2 — components/PollItem.tsx
- "Dit is een Client Component — zie
'use client'bovenaan." - "Het berekent percentages en toont bars. Copy-paste."
Stap 3.3 — components/VoteForm.tsx
- "Nog een Client Component. Hier wordt
votePoll()aangeroepen — de functie die jullie net geschreven hebben!" - "Copy-paste."
Stap 3.4 — app/poll/[id]/page.tsx
- "De detailpagina. Weer een Server Component met
await getPollById()." - "Copy-paste."
Test samen:
- "Open http://localhost:3000 — zien jullie polls?"
- "Klik op een poll — kun je stemmen?"
- "Check in Supabase dashboard: stijgt het aantal votes?"
Troubleshooting als het niet werkt:
- Lege pagina → RLS SELECT policy mist
Cannot find module→ check import paths- Stemmen werkt niet → vote_option RPC functie mist
10:15–10:30 | Pauze (15 min)
📌 Slide 7
Zeg voor de pauze: "Na de pauze gaan jullie zelfstandig Deel 4 doen: de /create pagina. De volledige UI staat in de PDF — jullie schrijven alleen de INSERT query."
10:30–10:45 | Uitleg INSERT + start Deel 4 (15 min)
📌 Slide 8 + PDF Deel 4 intro
Doel: INSERT concept uitleggen voordat ze zelfstandig aan de slag gaan.
Wat te zeggen:
- "Het formulier staat compleet in de PDF (Stap 4.3). Jullie hoeven alleen de handleSubmit functie in te vullen."
- "Maar eerst: Stap 4.1 — de RLS policy. Zonder INSERT policy blokkeert Supabase je."
Loop door Stap 4.1 (RLS policy):
- "Open Supabase dashboard → SQL Editor"
- "Voer de twee CREATE POLICY statements uit de PDF uit"
- "Dit is tijdelijk — volgende les beperken we dit met Auth"
Leg Stap 4.2 (INSERT theorie) uit:
- "Er zijn twee INSERT stappen:"
- Insert de poll:
.from("polls").insert({ question }).select().single()→ je krijgt de poll met id terug - Insert de options:
.from("options").insert(options.map(...))→ gebruik hetpoll.idvan stap 1
- Insert de poll:
- Toon op whiteboard:
1. INSERT poll → { id: 5, question: "..." } 2. INSERT options → [{ poll_id: 5, text: "A" }, { poll_id: 5, text: "B" }] 3. router.push("/") → terug naar homepage
Zeg: "Nu zijn jullie aan de beurt. Stap 4.3 in de PDF: copy-paste het hele bestand, en vul het TODO-blok in. Ik loop rond."
10:45–11:30 | ZELFSTANDIG: PDF Deel 4 — /create pagina (45 min)
📌 Studenten werken met PDF Stap 4.3
Wat ze doen:
app/create/page.tsxaanmaken met de code uit de PDF (Stap 4.3)- handleSubmit TODO-blok invullen (de INSERT logica)
- Testen: poll aanmaken → verschijnt op homepage
Jouw rol — loop rond en help:
Typische problemen:
- RLS INSERT policy vergeten → "new row violates row-level security" → Stap 4.1 niet gedaan
optionsniet gekoppeld aan poll →poll_idvergeten in de insertrouter.pushwerkt niet → checkimport { useRouter } from "next/navigation"(niet"next/router")- Form submit herlaadt pagina →
e.preventDefault()check pollis undefined na insert →.select().single()vergeten
Check-in na 15 min:
- "Wie heeft de RLS policy al toegevoegd?"
- "Wie kan al een poll aanmaken?"
Check-in na 30 min:
- "Wie heeft een werkende /create pagina?"
- Help studenten die vastlopen
Snelle studenten:
- Validatie toevoegen (vraag niet leeg, min 2 opties)
- Styling verbeteren met Tailwind
- Delete functionaliteit bouwen
11:30–12:00 | Vragen + Huiswerk (30 min)
📌 Slide 9, 10
Vragen beantwoorden:
- Open ronde: waar liepen jullie tegenaan?
- Concepten herhalen die onduidelijk waren
- Eventueel: laat een werkende /create pagina zien van een student
Huiswerk bespreken (Slide 9):
Verplicht:
- /create pagina afmaken (als niet klaar)
- Validatie toevoegen (vraag niet leeg, min 2 opties)
Extra:
- Delete functionaliteit
- SQL queries direct in Supabase testen
- Realtime subscriptions uittesten
- Styling verbeteren
Vooruitblik (Slide 10):
- "Volgende week: Supabase Auth"
- "Inloggen, registreren, en bepalen wie wat mag doen"
Troubleshooting Overzicht
| Probleem | Oorzaak | Oplossing |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL is undefined |
.env.local niet geladen | Dev server herstarten |
| Lege pagina, geen polls | RLS policy mist | SELECT policy toevoegen in Supabase |
| "new row violates row-level security" | INSERT policy mist | INSERT policy toevoegen |
Cannot find module '@/types' |
Import path fout | Check tsconfig.json paths |
PGRST202 Could not find the function public.vote_option |
vote_option SQL functie mist | Stap 1.5 uitvoeren in SQL Editor |
| Options verschijnen niet bij poll | Foreign key mismatch | Check poll_id in options tabel |
| Stemmen werkt niet | RPC functie mist | vote_option function aanmaken in SQL editor |
| Form submit herlaadt pagina | preventDefault mist | e.preventDefault() in handleSubmit |
| poll is undefined na insert | .select().single() mist | Toevoegen aan insert query |
Voorbereiding Checklist
- Eigen QuickPoll project werkt lokaal
- Supabase project met polls + options tabellen
- vote_option RPC functie aangemaakt
- SELECT RLS policies staan aan
- Lesopdracht PDF gedeeld met studenten (digitaal)
- Whiteboard/marker beschikbaar voor queries