# 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: 1. De Supabase JavaScript client installeren en configureren 2. Environment variables gebruiken voor API keys 3. Data ophalen via Supabase queries (select met relaties, eq, single) 4. Het verschil uitleggen tussen Server en Client Components 5. 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.local` aan 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: `createClient` maakt 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): ```sql 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: 1. **SELECT alles:** `.from("polls").select("*, options(*)")` β€” de `*` haalt alles op, `options(*)` volgt de relatie 2. **SELECT een:** `.eq("id", 5).single()` β€” filter + verwacht 1 resultaat 3. **INSERT:** `.insert({ question }).select().single()` β€” maak nieuw record, krijg het terug 4. **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:** ```typescript 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:** ```typescript 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:** ```typescript 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, kunnen `useState`/`onClick` gebruiken. 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 function` en `await getPolls()` β€” dit is een Server Component." - "Copy-paste uit de PDF." - "De `` 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:" 1. Insert de poll: `.from("polls").insert({ question }).select().single()` β†’ je krijgt de poll met id terug 2. Insert de options: `.from("options").insert(options.map(...))` β†’ gebruik het `poll.id` van stap 1 - 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:** 1. `app/create/page.tsx` aanmaken met de code uit de PDF (Stap 4.3) 2. handleSubmit TODO-blok invullen (de INSERT logica) 3. 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 - `options` niet gekoppeld aan poll β†’ `poll_id` vergeten in de insert - `router.push` werkt niet β†’ check `import { useRouter } from "next/navigation"` (niet `"next/router"`) - Form submit herlaadt pagina β†’ `e.preventDefault()` check - `poll` is 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