fix: les 9
This commit is contained in:
@@ -1,372 +1,105 @@
|
||||
# Les 7: Database Principles
|
||||
# Les 7: Van In-Memory naar Supabase (Introductie)
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 2: Technical Foundations** (Les 4-8)
|
||||
**Deel 2: Technical Foundations** (Les 4-9)
|
||||
|
||||
## Beschrijving
|
||||
Leer de basisprincipes van relationele databases voordat we Supabase gaan gebruiken. Begrijp tabellen, relaties, keys en normalisatie - essentiële kennis voor elke developer.
|
||||
Eerste kennismaking met Supabase. Studenten maken hun QuickPoll uit Les 6 af, leren wat een relationele database is via de Supabase GUI (geen SQL nodig), en koppelen voor het eerst een echte database aan hun Next.js app.
|
||||
|
||||
> **Reflectie na geven:** Te veel materiaal voor één les. Studenten kwamen niet door Deel 3 heen. Daarom is Les 8 toegevoegd waarin de Supabase-koppeling rustig opnieuw wordt doorlopen, plus een /create pagina.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Lesopbouw
|
||||
|
||||
### Wat is een Relationele Database?
|
||||
### Deel 1 — Poll afmaken (30 min, klassikaal)
|
||||
Onaffe stukken uit Les 6 wegwerken:
|
||||
- `votePoll()` functie in `data.ts`
|
||||
- POST route die echt verwerkt (geen `console.log` meer)
|
||||
- **Server Component + VoteForm split** — kernmoment van de les
|
||||
- GET route + visuele percentage bars in `PollItem`
|
||||
|
||||
**Een database is:** Een georganiseerde verzameling van data.
|
||||
### Deel 2 — Supabase Introductie, No Code (45 min)
|
||||
Voor de meeste studenten hun eerste database ervaring.
|
||||
- Wat is Supabase? Open-source Firebase alternatief op Postgres
|
||||
- Project aanmaken (via GitHub login)
|
||||
- `polls` tabel aanmaken via Table Editor (id, question, created_at)
|
||||
- `options` tabel aanmaken met foreign key naar polls (CASCADE)
|
||||
- RLS policies uitleggen en SELECT policies aanzetten
|
||||
- Testdata invoeren via de GUI
|
||||
- SQL Editor: eerste `SELECT`, `WHERE` en `JOIN` query
|
||||
|
||||
**Relationeel betekent:** Data is opgeslagen in tabellen die aan elkaar gerelateerd zijn.
|
||||
### Pauze (15 min)
|
||||
|
||||
**Vergelijk het met Excel:**
|
||||
- Database = Excel workbook
|
||||
- Tabel = Excel sheet
|
||||
- Kolom = Excel kolom (field)
|
||||
- Rij = Excel rij (record)
|
||||
### Deel 3 — Supabase koppelen aan Next.js (60 min)
|
||||
- `npm install @supabase/supabase-js`
|
||||
- `.env.local` met `NEXT_PUBLIC_SUPABASE_URL` + `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
- `lib/supabase.ts` client
|
||||
- Types updaten (`Poll`, `Option`)
|
||||
- `data.ts` herschrijven met Supabase queries (`select`, `eq`, `single`)
|
||||
- Homepage async maken
|
||||
- `PollItem`, `VoteForm`, detailpagina aanpassen
|
||||
- Testen op localhost — data overleeft restart
|
||||
|
||||
### Vragen + Huiswerk (15 min)
|
||||
**Huiswerk:** /create pagina bouwen (formulier → INSERT in Supabase) — voor de meeste studenten te zwaar gebleken, daarom in Les 8 herhaald.
|
||||
|
||||
---
|
||||
|
||||
### Tabellen, Kolommen en Rijen
|
||||
|
||||
**Voorbeeld: Users tabel**
|
||||
|
||||
| id | name | email | created_at |
|
||||
|----|------|-------|------------|
|
||||
| 1 | Tim | tim@email.com | 2024-01-15 |
|
||||
| 2 | Anna | anna@email.com | 2024-01-16 |
|
||||
| 3 | Jan | jan@email.com | 2024-01-17 |
|
||||
|
||||
**Terminologie:**
|
||||
- **Tabel:** users
|
||||
- **Kolommen:** id, name, email, created_at
|
||||
- **Rijen:** 3 records (Tim, Anna, Jan)
|
||||
- **Cell:** Eén specifieke waarde (bijv. "tim@email.com")
|
||||
|
||||
---
|
||||
|
||||
### Data Types
|
||||
|
||||
Elke kolom heeft een type:
|
||||
|
||||
| Type | Beschrijving | Voorbeeld |
|
||||
|------|--------------|-----------|
|
||||
| `text` / `varchar` | Tekst | "Tim", "Hello world" |
|
||||
| `integer` / `int` | Hele getallen | 1, 42, -5 |
|
||||
| `decimal` / `numeric` | Decimalen | 19.99, 3.14 |
|
||||
| `boolean` | True/False | true, false |
|
||||
| `timestamp` | Datum + tijd | 2024-01-15 14:30:00 |
|
||||
| `uuid` | Unieke identifier | a1b2c3d4-e5f6-... |
|
||||
|
||||
**Kies het juiste type:**
|
||||
- Prijs? → `decimal` (niet `integer`, want centen)
|
||||
- Is actief? → `boolean`
|
||||
- Naam? → `text`
|
||||
- Aantal? → `integer`
|
||||
|
||||
---
|
||||
|
||||
### Primary Keys
|
||||
|
||||
**Wat:** Een kolom die elke rij UNIEK identificeert.
|
||||
|
||||
**Regels:**
|
||||
- Moet uniek zijn per rij
|
||||
- Mag nooit NULL zijn
|
||||
- Verandert nooit
|
||||
|
||||
**Voorbeeld:**
|
||||
```
|
||||
users
|
||||
------
|
||||
id (PRIMARY KEY) | name | email
|
||||
1 | Tim | tim@email.com
|
||||
2 | Anna | anna@email.com
|
||||
```
|
||||
|
||||
**Waarom niet `email` als primary key?**
|
||||
- Emails kunnen veranderen
|
||||
- `id` is stabiel en snel
|
||||
|
||||
---
|
||||
|
||||
### Foreign Keys
|
||||
|
||||
**Wat:** Een kolom die verwijst naar de primary key van een andere tabel.
|
||||
|
||||
**Voorbeeld: Posts tabel**
|
||||
```
|
||||
posts
|
||||
------
|
||||
id | title | user_id (FOREIGN KEY → users.id)
|
||||
1 | "Mijn blog" | 1
|
||||
2 | "Hello world" | 1
|
||||
3 | "Tips" | 2
|
||||
```
|
||||
|
||||
**Wat zegt dit?**
|
||||
- Post 1 en 2 zijn van user 1 (Tim)
|
||||
- Post 3 is van user 2 (Anna)
|
||||
|
||||
---
|
||||
|
||||
### Relatie Types
|
||||
|
||||
**One-to-Many (1:N)** - Meest voorkomend!
|
||||
```
|
||||
Eén user → meerdere posts
|
||||
Eén category → meerdere products
|
||||
```
|
||||
|
||||
**One-to-One (1:1)** - Zeldzaam
|
||||
```
|
||||
Eén user → één profile
|
||||
```
|
||||
|
||||
**Many-to-Many (N:N)** - Via tussentabel
|
||||
```
|
||||
Posts ↔ Tags (een post heeft meerdere tags, een tag heeft meerdere posts)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### One-to-Many Voorbeeld
|
||||
|
||||
```
|
||||
users posts
|
||||
------ ------
|
||||
id | name id | title | user_id
|
||||
1 | Tim ←────────── 1 | "Blog 1" | 1
|
||||
2 | Anna ←────┬───── 2 | "Blog 2" | 1
|
||||
└───── 3 | "Tips" | 2
|
||||
```
|
||||
|
||||
**Lees:** Tim heeft 2 posts, Anna heeft 1 post.
|
||||
|
||||
---
|
||||
|
||||
### Many-to-Many met Tussentabel
|
||||
|
||||
```
|
||||
posts post_tags tags
|
||||
------ --------- ------
|
||||
id | title post_id | tag_id id | name
|
||||
1 | "React tips" 1 | 1 1 | "react"
|
||||
2 | "CSS guide" 1 | 2 2 | "frontend"
|
||||
2 | 2 3 | "css"
|
||||
2 | 3
|
||||
```
|
||||
|
||||
**Lees:**
|
||||
- Post 1 heeft tags: react, frontend
|
||||
- Post 2 heeft tags: frontend, css
|
||||
|
||||
---
|
||||
|
||||
### Normalisatie Basics
|
||||
|
||||
**Probleem: Data duplicatie**
|
||||
```
|
||||
orders (SLECHT)
|
||||
------
|
||||
id | customer_name | customer_email | product_name | price
|
||||
1 | Tim | tim@email.com | Laptop | 999
|
||||
2 | Tim | tim@email.com | Phone | 699
|
||||
3 | Anna | anna@email.com | Laptop | 999
|
||||
```
|
||||
|
||||
**Problemen:**
|
||||
- Tim's email staat 2x (als hij verandert: 2 plekken updaten)
|
||||
- "Laptop" en prijs staan 2x
|
||||
|
||||
---
|
||||
|
||||
### Genormaliseerde Versie
|
||||
|
||||
```
|
||||
users products orders
|
||||
------ -------- ------
|
||||
id | name | email id | name | price id | user_id | product_id
|
||||
1 | Tim | tim@... 1 | Laptop | 999 1 | 1 | 1
|
||||
2 | Anna | anna@... 2 | Phone | 699 2 | 1 | 2
|
||||
3 | 2 | 1
|
||||
```
|
||||
|
||||
**Voordelen:**
|
||||
- Elk gegeven staat 1x
|
||||
- Update op 1 plek
|
||||
- Minder opslagruimte
|
||||
|
||||
---
|
||||
|
||||
### NULL Values
|
||||
|
||||
**NULL = "geen waarde" (niet 0, niet "")**
|
||||
|
||||
```
|
||||
users
|
||||
------
|
||||
id | name | phone
|
||||
1 | Tim | 0612345678
|
||||
2 | Anna | NULL ← Geen telefoon bekend
|
||||
```
|
||||
|
||||
**Wanneer NULL toestaan?**
|
||||
- Optionele velden (phone, description)
|
||||
- Niet bij verplichte velden (name, email)
|
||||
|
||||
---
|
||||
|
||||
### Defaults
|
||||
|
||||
**Automatische waarde als je niks opgeeft:**
|
||||
|
||||
```
|
||||
todos
|
||||
------
|
||||
id | title | completed | created_at
|
||||
| | DEFAULT: false | DEFAULT: now()
|
||||
```
|
||||
|
||||
Bij `INSERT INTO todos (title) VALUES ('Test')`:
|
||||
```
|
||||
id | title | completed | created_at
|
||||
1 | Test | false | 2024-01-15 10:30:00
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Database Schema Tekenen
|
||||
|
||||
**Tools:** draw.io, Excalidraw, pen en papier
|
||||
|
||||
**Conventie:**
|
||||
```
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ users │ │ posts │
|
||||
├──────────────┤ ├──────────────┤
|
||||
│ id (PK) │───┐ │ id (PK) │
|
||||
│ name │ │ │ title │
|
||||
│ email │ └────→│ user_id (FK) │
|
||||
│ created_at │ │ content │
|
||||
└──────────────┘ │ created_at │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
PK = Primary Key
|
||||
FK = Foreign Key
|
||||
Pijl = Relatie richting
|
||||
## Kernconcepten
|
||||
- Server Component vs Client Component patroon
|
||||
- Database-as-a-service (Supabase = Postgres + Auth + REST + Realtime)
|
||||
- Tabellen, kolommen, primary keys, foreign keys, CASCADE
|
||||
- Row Level Security (RLS) basics
|
||||
- Environment variables in Next.js (`NEXT_PUBLIC_` prefix)
|
||||
- Supabase JavaScript client: `from`, `select`, `eq`, `single`
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Pen en papier / Excalidraw / draw.io
|
||||
- Supabase Table Editor (vooruitblik)
|
||||
- Next.js 15
|
||||
- Supabase (Table Editor, SQL Editor)
|
||||
- Cursor / VS Code
|
||||
- TypeScript
|
||||
- `@supabase/supabase-js`
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Lesopdracht
|
||||
Volg de docent klassikaal mee door Deel 1 → 3. Aan het eind draait je QuickPoll app met data uit Supabase.
|
||||
|
||||
### Database Design Oefening
|
||||
|
||||
**Deel 1: Blog Database Ontwerpen (45 min)**
|
||||
|
||||
Ontwerp een database voor een blog met:
|
||||
- Users (kunnen posts schrijven)
|
||||
- Posts (hebben een auteur)
|
||||
- Comments (op posts, door users)
|
||||
|
||||
Voor elke tabel:
|
||||
1. Teken de tabel met kolommen
|
||||
2. Bepaal data types
|
||||
3. Markeer Primary Keys
|
||||
4. Markeer Foreign Keys
|
||||
5. Teken de relaties
|
||||
|
||||
**Deel 2: Normalisatie Oefening (30 min)**
|
||||
|
||||
Gegeven deze "slechte" tabel:
|
||||
|
||||
```
|
||||
library
|
||||
-------
|
||||
book_title | author_name | author_email | borrower_name | borrowed_date
|
||||
"1984" | "Orwell" | orwell@... | "Tim" | 2024-01-15
|
||||
"1984" | "Orwell" | orwell@... | "Anna" | 2024-01-10
|
||||
"Dune" | "Herbert" | herbert@... | "Tim" | 2024-01-12
|
||||
```
|
||||
|
||||
Normaliseer naar aparte tabellen:
|
||||
1. Welke entiteiten zie je?
|
||||
2. Maak aparte tabellen
|
||||
3. Voeg relaties toe
|
||||
|
||||
**Deel 3: Eindproject Schema (45 min)**
|
||||
|
||||
Ontwerp het database schema voor jouw eindproject:
|
||||
1. Welke entiteiten heb je nodig?
|
||||
2. Teken elke tabel met kolommen
|
||||
3. Bepaal relaties
|
||||
4. Documenteer je keuzes
|
||||
|
||||
### Deliverable
|
||||
- Blog database schema (tekening)
|
||||
- Genormaliseerde library database
|
||||
- Eindproject database schema
|
||||
## Huiswerk
|
||||
- /create pagina afmaken (formulier dat een nieuwe poll insert)
|
||||
- Validatie toevoegen
|
||||
- Eventueel: extra SQL queries proberen
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Verdieping en Voorbereiding
|
||||
|
||||
**Deel 1: Eindproject Schema Uitwerken (1 uur)**
|
||||
|
||||
Werk je database schema volledig uit:
|
||||
|
||||
1. **Per tabel:**
|
||||
- Naam
|
||||
- Alle kolommen met data types
|
||||
- Primary key
|
||||
- Foreign keys
|
||||
- Defaults
|
||||
- Nullable fields
|
||||
|
||||
2. **Documenteer:**
|
||||
- Waarom deze structuur?
|
||||
- Welke relaties?
|
||||
- Eventuele alternatieve overwegingen
|
||||
|
||||
**Deel 2: Supabase Account (30 min)**
|
||||
|
||||
Bereid je voor op volgende les:
|
||||
1. Maak account op [supabase.com](https://supabase.com)
|
||||
2. Verken de interface
|
||||
3. Bekijk de Table Editor
|
||||
|
||||
**Deel 3: Reflectie (30 min)**
|
||||
|
||||
Beantwoord deze vragen (kort):
|
||||
1. Wat is het verschil tussen primary en foreign key?
|
||||
2. Waarom normaliseren we data?
|
||||
3. Wanneer gebruik je one-to-many vs many-to-many?
|
||||
4. Welke tabellen heeft jouw eindproject nodig?
|
||||
|
||||
### Deliverable
|
||||
- Volledig uitgewerkt database schema voor eindproject
|
||||
- Supabase account aangemaakt
|
||||
- Reflectie vragen beantwoord
|
||||
## Lesmateriaal
|
||||
- `Les07-Slide-Overzicht.md`
|
||||
- `Les07-Docenttekst.md`
|
||||
- `Les07-Live-Coding-Guide.md`
|
||||
- `Les07-Lesopdracht.pdf`
|
||||
- `Les07-Slides.pptx`
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Uitleggen wat een relationele database is
|
||||
- Tabellen, kolommen en rijen beschrijven
|
||||
- De juiste data types kiezen
|
||||
- Primary keys en hun doel uitleggen
|
||||
- Foreign keys en relaties begrijpen
|
||||
- One-to-many en many-to-many relaties herkennen
|
||||
- Het probleem van data duplicatie identificeren
|
||||
- Een database normaliseren
|
||||
- NULL values en defaults begrijpen
|
||||
- Een database schema ontwerpen en tekenen
|
||||
- Uitleggen wat Supabase is en wanneer je het inzet
|
||||
- Een Supabase project aanmaken via de GUI
|
||||
- Tabellen met relaties (foreign keys) aanmaken in de Table Editor
|
||||
- RLS policies opzetten voor publieke read access
|
||||
- De Supabase JavaScript client installeren en configureren
|
||||
- Environment variables gebruiken voor API keys
|
||||
- Data ophalen met `select`, `eq` en relaties (`options(*)`)
|
||||
- Het Server Component + Client Component patroon toepassen
|
||||
|
||||
---
|
||||
|
||||
## Lessons Learned (voor v2)
|
||||
- 3 hoofddelen in 1 les is te ambitieus voor het tempo van deze klas
|
||||
- /create pagina als huiswerk werkt niet — studenten hebben begeleiding nodig bij hun eerste INSERT
|
||||
- Splits Supabase introductie en concrete code-koppeling over twee lessen → **Les 8 voegt dit toe**
|
||||
|
||||
@@ -1,305 +1,116 @@
|
||||
# Les 8: Supabase: Auth & CRUD
|
||||
# Les 8: Van In-Memory naar Supabase (Self-Paced + /create)
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 2: Technical Foundations** (Les 4-8)
|
||||
**Deel 2: Technical Foundations** (Les 4-9)
|
||||
|
||||
## Beschrijving
|
||||
Supabase Authentication en CRUD operaties. Implementeer email/password auth, JWT tokens, Row Level Security (RLS) policies, realtime subscriptions en volledige CRUD functionaliteit in je full-stack app.
|
||||
Vervolg op Les 7. Studenten kregen vorige les niet alles af, dus loopt de docent de Supabase-koppeling rustig opnieuw door — nu in een **self-paced PDF** met copy-paste blokken en TODO-blokken. Daarna bouwen studenten zelfstandig de **/create pagina** (hun eerste INSERT).
|
||||
|
||||
**Aanpak:** Deel 1-3 doet de docent klassikaal door de PDF heen. Deel 4 (de /create pagina) doen studenten zelfstandig met de docent als coach.
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Lesopbouw (3 uur)
|
||||
|
||||
### Wat is Supabase?
|
||||
|
||||
**Supabase = Database + Auth in één**
|
||||
- PostgreSQL database (gratis tier: 500MB)
|
||||
- Ingebouwde authenticatie
|
||||
- Real-time subscriptions
|
||||
- File storage
|
||||
- Auto-generated API
|
||||
|
||||
**Waarom Supabase voor beginners:**
|
||||
- Geen eigen server nodig
|
||||
- Visuele Table Editor (geen SQL kennis nodig)
|
||||
- Simpele JavaScript SDK
|
||||
- Gratis tier is ruim voldoende
|
||||
| Tijd | Onderwerp | Vorm |
|
||||
|------|-----------|------|
|
||||
| 09:00–09:15 | Welkom + Intro + PDF uitdelen | Klassikaal |
|
||||
| 09:15–09:45 | **PDF Deel 1** — Setup (npm, .env, supabase.ts, types, vote_option SQL) | Klassikaal |
|
||||
| 09:45–10:00 | **PDF Deel 2** — Queries (`getPolls`, `getPollById`, `votePoll`) | Klassikaal |
|
||||
| 10:00–10:15 | **PDF Deel 3** — Componenten (page, PollItem, VoteForm, [id]) | Klassikaal |
|
||||
| 10:15–10:30 | Pauze | — |
|
||||
| 10:30–10:45 | Uitleg INSERT theorie + RLS policy + start Deel 4 | Klassikaal |
|
||||
| 10:45–11:30 | **PDF Deel 4** — /create pagina bouwen | **Zelfstandig** |
|
||||
| 11:30–12:00 | Vragen + Huiswerk + Vooruitblik Auth | Klassikaal |
|
||||
|
||||
---
|
||||
|
||||
### Supabase Project Aanmaken
|
||||
## Lesopdracht (PDF)
|
||||
Studenten krijgen één PDF met vier delen. Grijze blokken zijn copy-paste, gele TODO-blokken vullen ze zelf in.
|
||||
|
||||
**Stap 1:** Ga naar [supabase.com](https://supabase.com) en maak account
|
||||
### Deel 1 — Setup (klassikaal)
|
||||
1. `npm install @supabase/supabase-js`
|
||||
2. `.env.local` met SUPABASE URL + ANON KEY
|
||||
3. `lib/supabase.ts` client
|
||||
4. `types/index.ts` updaten (`Poll`, `Option`)
|
||||
5. **vote_option SQL functie** aanmaken in Supabase SQL Editor (voorkomt PGRST202 error)
|
||||
|
||||
**Stap 2:** Klik "New Project"
|
||||
- Naam: `todo-app`
|
||||
- Database Password: (bewaar deze!)
|
||||
- Region: `West EU (Frankfurt)` (dichtst bij NL)
|
||||
### Deel 2 — Queries (klassikaal, TODO's invullen)
|
||||
- `getPolls()` — `select("*, options(*)")`
|
||||
- `getPollById(id)` — `eq("id", id).single()`
|
||||
- `votePoll(optionId)` — `rpc("vote_option", { option_id })`
|
||||
|
||||
**Stap 3:** Wacht ~2 minuten tot project klaar is
|
||||
### Deel 3 — Componenten (klassikaal, copy-paste)
|
||||
- `app/page.tsx` (async Server Component)
|
||||
- `components/PollItem.tsx` (Client, percentage bars)
|
||||
- `components/VoteForm.tsx` (Client)
|
||||
- `app/poll/[id]/page.tsx` (Server Component, **Next.js 15 `params` is een Promise**)
|
||||
|
||||
**Stap 4:** Ga naar Settings → API en kopieer:
|
||||
- `Project URL`
|
||||
- `anon public` key
|
||||
### Deel 4 — /create pagina (zelfstandig)
|
||||
1. RLS INSERT policy voor `polls` en `options`
|
||||
2. INSERT theorie (poll inserten → id terugkrijgen → options met `poll_id`)
|
||||
3. `app/create/page.tsx` — volledig formulier in PDF, alleen `handleSubmit` TODO invullen
|
||||
|
||||
---
|
||||
|
||||
### Je Database Schema Implementeren
|
||||
|
||||
In Les 7 heb je een database schema ontworpen. Nu gaan we dat implementeren!
|
||||
|
||||
**In Supabase Dashboard → Table Editor:**
|
||||
|
||||
1. Klik "New Table"
|
||||
2. Gebruik je schema uit Les 7
|
||||
3. Voeg kolommen toe met de juiste types
|
||||
4. Definieer Primary Keys en Foreign Keys
|
||||
|
||||
**Voorbeeld: todos tabel**
|
||||
|
||||
| Name | Type | Default | Primary |
|
||||
|------|------|---------|---------|
|
||||
| id | int8 | - | ✓ (auto) |
|
||||
| title | text | - | |
|
||||
| completed | bool | false | |
|
||||
| created_at | timestamptz | now() | |
|
||||
|
||||
---
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**Wat zijn environment variables?**
|
||||
- Configuratie die NIET in je code hoort
|
||||
- API keys, database URLs, secrets
|
||||
- Verschillend per omgeving (lokaal vs productie)
|
||||
|
||||
**Maak `.env.local` in je project root:**
|
||||
```bash
|
||||
# .env.local - NOOIT committen naar Git!
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx
|
||||
```
|
||||
|
||||
**Maak ook `.env.example` (WEL committen):**
|
||||
```bash
|
||||
# .env.example - template voor anderen
|
||||
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Supabase SDK Installeren
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
**Maak `src/lib/supabase.ts`:**
|
||||
```typescript
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CRUD Operaties
|
||||
|
||||
**C - Create (toevoegen):**
|
||||
```typescript
|
||||
const { data, error } = await supabase
|
||||
.from('todos')
|
||||
.insert({ title: 'Nieuwe taak' })
|
||||
```
|
||||
|
||||
**R - Read (ophalen):**
|
||||
```typescript
|
||||
const { data, error } = await supabase
|
||||
.from('todos')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false })
|
||||
```
|
||||
|
||||
**U - Update (wijzigen):**
|
||||
```typescript
|
||||
const { data, error } = await supabase
|
||||
.from('todos')
|
||||
.update({ completed: true })
|
||||
.eq('id', todoId)
|
||||
```
|
||||
|
||||
**D - Delete (verwijderen):**
|
||||
```typescript
|
||||
const { error } = await supabase
|
||||
.from('todos')
|
||||
.delete()
|
||||
.eq('id', todoId)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Authenticatie met Auth UI
|
||||
|
||||
**Installeer auth packages:**
|
||||
```bash
|
||||
npm install @supabase/auth-ui-react @supabase/auth-ui-shared
|
||||
```
|
||||
|
||||
**Login component:**
|
||||
```tsx
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||
import { supabase } from '@/lib/supabase'
|
||||
|
||||
export function LoginForm() {
|
||||
return (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
providers={[]}
|
||||
magicLink={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Huidige user checken:**
|
||||
```typescript
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
|
||||
if (user) {
|
||||
// User is ingelogd
|
||||
console.log('Logged in as:', user.email)
|
||||
} else {
|
||||
// Redirect naar login
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Deployment naar Vercel
|
||||
|
||||
**Stap 1: Push naar GitHub**
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Add Supabase integration"
|
||||
git push
|
||||
```
|
||||
|
||||
**Stap 2: Deploy op Vercel**
|
||||
1. Ga naar [vercel.com](https://vercel.com)
|
||||
2. "Add New Project"
|
||||
3. Import je GitHub repo
|
||||
4. **BELANGRIJK:** Voeg Environment Variables toe!
|
||||
- `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
5. Klik "Deploy"
|
||||
|
||||
**Stap 3: Supabase Redirect URLs**
|
||||
1. Ga naar Supabase → Authentication → URL Configuration
|
||||
2. Voeg toe bij "Redirect URLs":
|
||||
- `https://jouw-app.vercel.app/**`
|
||||
## Belangrijke leermomenten
|
||||
- **Self-paced PDF werkt beter dan live coding** voor deze klas — studenten kunnen op eigen tempo
|
||||
- **Next.js 15 breaking change:** `params` is nu een `Promise<{ id: string }>`. Je moet `const { id } = await params`
|
||||
- **Supabase RPC functies:** `vote_option` aanmaken in SQL Editor — anders PGRST202 error
|
||||
- **RLS policies:** Zonder INSERT policy faalt elke `.insert()` met "row-level security violation"
|
||||
- **Server vs Client Components:** Server haalt data (`async` + `await`), Client doet interactie (`'use client'` + `useState`)
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- Supabase
|
||||
- Next.js
|
||||
- OpenCode/WebStorm
|
||||
- Vercel
|
||||
- Git
|
||||
- Next.js 15
|
||||
- Supabase (SQL Editor, Table Editor, RLS)
|
||||
- `@supabase/supabase-js`
|
||||
- TypeScript
|
||||
- Cursor / VS Code
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2 uur)
|
||||
## Huiswerk
|
||||
**Verplicht:**
|
||||
- /create pagina afmaken (als niet klaar)
|
||||
- Validatie toevoegen (vraag niet leeg, minimaal 2 opties)
|
||||
|
||||
### Bouw een Todo App met Supabase
|
||||
|
||||
**Groepsdiscussie (15 min):**
|
||||
Bespreek klassikaal de database schemas uit Les 7 - wie heeft welke structuur gekozen en waarom?
|
||||
|
||||
**Deel 1: Supabase Setup (30 min)**
|
||||
|
||||
1. Maak Supabase account en project
|
||||
2. Maak je tabellen via Table Editor (gebaseerd op Les 7 schema)
|
||||
3. Kopieer credentials
|
||||
4. Installeer `@supabase/supabase-js`
|
||||
5. Maak `src/lib/supabase.ts`
|
||||
6. Configureer `.env.local`
|
||||
|
||||
Test: `npm run dev` werkt zonder errors
|
||||
|
||||
**Deel 2: CRUD Interface (1 uur)**
|
||||
|
||||
Bouw UI voor todos:
|
||||
1. Lijst van todos tonen
|
||||
2. Form om nieuwe todo toe te voegen
|
||||
3. Checkbox om todo af te vinken
|
||||
4. Delete button per todo
|
||||
|
||||
Gebruik AI hulp voor de components!
|
||||
|
||||
**Deel 3: Authenticatie (30 min)**
|
||||
|
||||
1. Installeer auth packages
|
||||
2. Maak login pagina met Auth UI
|
||||
3. Toon alleen app voor ingelogde users
|
||||
4. Test: login met magic link
|
||||
|
||||
### Deliverable
|
||||
- Werkende Todo app lokaal
|
||||
- GitHub repository met code
|
||||
- Screenshot van werkende app
|
||||
**Extra:**
|
||||
- Delete functionaliteit
|
||||
- SQL queries direct testen in Supabase
|
||||
- Realtime subscriptions uitproberen
|
||||
- Styling polish
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (2 uur)
|
||||
|
||||
### Deploy naar Productie + Uitbreiden
|
||||
|
||||
**Deel 1: Deployment (30 min)**
|
||||
|
||||
1. Push naar GitHub
|
||||
2. Deploy naar Vercel
|
||||
3. Configureer env vars in Vercel
|
||||
4. Voeg Vercel URL toe aan Supabase Redirect URLs
|
||||
5. Test: app werkt op productie URL!
|
||||
|
||||
**Deel 2: Features Uitbreiden (1 uur)**
|
||||
|
||||
Voeg toe:
|
||||
1. Filter buttons: Alle / Actief / Voltooid
|
||||
2. Sorteer op datum (nieuwste eerst)
|
||||
3. Loading state tijdens data ophalen
|
||||
4. Error state bij problemen
|
||||
5. Empty state: "Geen todos gevonden"
|
||||
|
||||
**Deel 3: Polish (30 min)**
|
||||
|
||||
1. Styling verbeteren met Tailwind
|
||||
2. Responsive design (mobile friendly)
|
||||
3. Kleine animaties (fade in/out)
|
||||
|
||||
### Deliverable
|
||||
- Deployed app op Vercel (werkende URL!)
|
||||
- Alle features werken in productie
|
||||
- Screenshot van productie app
|
||||
## Lesmateriaal
|
||||
- `Les08-Slide-Overzicht.md`
|
||||
- `Les08-Docenttekst.md`
|
||||
- `Les08-Lesopdracht.pdf` (de self-paced handleiding)
|
||||
- `Les08-Slides.pptx`
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Een Supabase project aanmaken en configureren
|
||||
- Database schema implementeren via Table Editor
|
||||
- Environment variables correct beheren
|
||||
- De Supabase client installeren en configureren
|
||||
- CRUD operaties uitvoeren met de Supabase SDK
|
||||
- Authenticatie implementeren met Auth UI
|
||||
- Deployen naar Vercel met environment variables
|
||||
- Database principles uit Les 7 toepassen in de praktijk
|
||||
- De Supabase JavaScript client installeren en configureren
|
||||
- Environment variables gebruiken voor API keys
|
||||
- Data ophalen via Supabase queries (`select` met relaties, `eq`, `single`)
|
||||
- Een PostgreSQL function aanroepen via `.rpc()`
|
||||
- Het verschil uitleggen tussen Server en Client Components in Next.js 15
|
||||
- Een formulier bouwen dat data INSERT in Supabase
|
||||
- RLS policies schrijven voor publieke read en insert
|
||||
- Veelvoorkomende Supabase errors herkennen en oplossen (PGRST202, RLS violation)
|
||||
|
||||
---
|
||||
|
||||
## Voorbereiding docent
|
||||
- [ ] QuickPoll project werkt lokaal op localhost:3000
|
||||
- [ ] Supabase project met `polls` + `options` tabellen
|
||||
- [ ] `vote_option` RPC functie aangemaakt
|
||||
- [ ] SELECT RLS policies staan aan
|
||||
- [ ] Lesopdracht PDF gedeeld met studenten (digitaal)
|
||||
- [ ] Whiteboard voor de vier query-patronen
|
||||
|
||||
@@ -1,126 +1,96 @@
|
||||
# Les 9: Full-Stack Mini Project
|
||||
# Les 9: Cursor Deep Dive (Student Plan)
|
||||
|
||||
---
|
||||
|
||||
## Hoofdstuk
|
||||
**Deel 3: Integration & AI Tooling** (Les 9-12)
|
||||
**Deel 2: Technical Foundations** (Les 4-9)
|
||||
|
||||
## Beschrijving
|
||||
Combineer alles wat je geleerd hebt (TypeScript, Next.js, Supabase) in een kleine werkende applicatie. Dit is je eerste "echte" full-stack project en voorbereiding op het werken met AI tools.
|
||||
Studenten hebben Cursor met het Student Plan. Tijd voor een deep dive in de 5 professionele workflows: **Rules & Skills, Plan Mode, Agent Mode, Debug Mode, en Code Review & Testing**. We bouwen een nieuwe app from scratch: **LinkVault** (bookmark manager, in-memory, Next.js 16 + TypeScript + Tailwind).
|
||||
|
||||
---
|
||||
|
||||
## Te Behandelen
|
||||
## Lesopbouw (3 uur)
|
||||
|
||||
### Groepsdiscussie (15 min)
|
||||
Bespreek klassikaal de Supabase ervaringen uit Les 8 - welke uitdagingen kwamen jullie tegen bij authenticatie en RLS?
|
||||
|
||||
### Doel van deze les
|
||||
|
||||
Je hebt nu alle bouwstenen:
|
||||
- TypeScript (Les 4)
|
||||
- Next.js met App Router (Les 5-6)
|
||||
- Supabase database & auth (Les 7-8)
|
||||
|
||||
Vandaag combineren we dit in een **werkende mini-app**. Geen nieuwe concepten - alleen integratie en toepassing.
|
||||
| Tijd | Onderwerp | Vorm |
|
||||
|------|-----------|------|
|
||||
| 09:00–09:10 | Welkom + terugblik | Klassikaal |
|
||||
| 09:10–09:35 | Theorie: Agent Harness, Rules, Plan/Debug/Review Mode | Klassikaal |
|
||||
| 09:35–09:55 | Setup: nieuw project + 3 `.cursor/rules` bestanden | Samen |
|
||||
| 09:55–10:15 | Plan Mode: plan LinkVault → review → Build | Klassikaal |
|
||||
| 10:15–10:30 | Pauze | — |
|
||||
| 10:30–11:05 | Agent Mode: features bouwen | Klassikaal |
|
||||
| 11:05–11:25 | Debug Mode: deterministische bug vinden en fixen | Klassikaal |
|
||||
| 11:25–11:45 | Code Review & Testing + semantic commits + git push | Klassikaal |
|
||||
| 11:45–11:55 | (Optioneel) PR review met Cursor | Klassikaal |
|
||||
| 11:55–12:00 | Afsluiting + huiswerk | Klassikaal |
|
||||
|
||||
---
|
||||
|
||||
### Mini Project: Personal Bookmarks
|
||||
## De 5 Kernvaardigheden
|
||||
|
||||
Een simpele bookmark manager waar je links kunt opslaan.
|
||||
### 1. Rules & Skills
|
||||
- `.cursor/rules/` directory met `.mdc` bestanden (YAML frontmatter)
|
||||
- 3 bestanden: `project.mdc` (alwaysApply), `components.mdc` (globs: tsx), `styles.mdc` (globs: css/tsx)
|
||||
- Houd rules kort, specifiek, en wijs naar voorbeelden
|
||||
|
||||
**Features:**
|
||||
- Login met Supabase Auth
|
||||
- Bookmarks toevoegen (URL + titel)
|
||||
- Bookmarks bekijken en verwijderen
|
||||
### 2. Plan Mode
|
||||
- `Shift+Tab` → agent researcht, stelt vragen, maakt interactief plan
|
||||
- Plan bewerken in markdown → "Build" klikken
|
||||
- Start elke feature met een plan
|
||||
|
||||
**Tech stack:**
|
||||
- Next.js 14 met App Router
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- Supabase (auth + database)
|
||||
- React Query
|
||||
### 3. Agent Mode
|
||||
- Agent = uitvoeren (files, terminal, multi-file). Ask = alleen vragen.
|
||||
- Goede context geven: `@file`, `@folder`, `@codebase`, `@docs`
|
||||
- Itereren met screenshots en feedback
|
||||
|
||||
---
|
||||
### 4. Debug Mode
|
||||
- Voor complexe bugs: hypotheses → logging → reproduceren → analyseren → fix
|
||||
- Tim introduceert bug handmatig (deterministic) → studenten debuggen
|
||||
|
||||
### Stap-voor-stap
|
||||
|
||||
#### Database Schema
|
||||
|
||||
**Tabel: bookmarks**
|
||||
| Kolom | Type |
|
||||
|-------|------|
|
||||
| id | uuid (PK) |
|
||||
| user_id | uuid (FK naar auth.users) |
|
||||
| url | text |
|
||||
| title | text |
|
||||
| created_at | timestamptz |
|
||||
|
||||
**RLS:** Users kunnen alleen eigen bookmarks zien, toevoegen en verwijderen.
|
||||
|
||||
#### Wat je bouwt
|
||||
|
||||
1. **Login pagina** - Supabase Auth
|
||||
2. **Dashboard** - Lijst van bookmarks
|
||||
3. **Add form** - URL + titel invoeren
|
||||
4. **Delete** - Bookmark verwijderen
|
||||
### 5. Code Review & Testing
|
||||
- "Find Issues" voor self-review
|
||||
- Tests schrijven met Agent Mode
|
||||
- Semantic commits: achteraf changes opdelen in logische groepen
|
||||
- (Optioneel) PR review met Cursor
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
- VS Code
|
||||
- Supabase Dashboard
|
||||
- Browser DevTools
|
||||
- Cursor (Student Plan)
|
||||
- Next.js 16
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
|
||||
---
|
||||
|
||||
## Lesopdracht (2.5 uur)
|
||||
|
||||
### Bouw de Bookmark Manager
|
||||
|
||||
**Checkpoints:**
|
||||
|
||||
| Tijd | Wat klaar moet zijn |
|
||||
|------|---------------------|
|
||||
| 30 min | Project setup + database schema |
|
||||
| 60 min | Auth werkt (login/logout) |
|
||||
| 90 min | Bookmarks toevoegen en bekijken |
|
||||
| 120 min | Delete werkt |
|
||||
| 150 min | Styling en testen |
|
||||
|
||||
**Minimale eisen:**
|
||||
- [ ] Login/logout werkt
|
||||
- [ ] Bookmarks toevoegen werkt
|
||||
- [ ] Bookmarks worden getoond
|
||||
- [ ] Delete werkt
|
||||
|
||||
### Deliverable
|
||||
- Werkende lokale applicatie
|
||||
- Screenshot van je app met data
|
||||
|
||||
---
|
||||
|
||||
## Huiswerk (1 uur)
|
||||
|
||||
### Reflectie
|
||||
|
||||
Schrijf korte reflectie (200 woorden):
|
||||
- Wat ging goed bij het integreren?
|
||||
- Waar liep je vast?
|
||||
- Wat zou je volgende keer anders doen?
|
||||
|
||||
Maak een lijst van 3 verbeterpunten voor je code.
|
||||
|
||||
### Deliverable
|
||||
- Reflectie (200 woorden)
|
||||
- 3 verbeterpunten
|
||||
## Lesmateriaal
|
||||
- `Les09-Slide-Overzicht.md`
|
||||
- `Les09-Docenttekst.md`
|
||||
- `Les09-Huiswerk.md`
|
||||
|
||||
---
|
||||
|
||||
## Leerdoelen
|
||||
Na deze les kan de student:
|
||||
- Een complete full-stack applicatie bouwen met Next.js, TypeScript en Supabase
|
||||
- CRUD operaties implementeren met React Query
|
||||
- Authenticatie integreren in een applicatie
|
||||
- Zelfstandig integratieproblemen oplossen
|
||||
- Uitleggen hoe een coding agent werkt (harness: instructions + tools + model)
|
||||
- `.cursor/rules` opzetten met meerdere bestanden (alwaysApply, globs)
|
||||
- Plan Mode gebruiken om een feature te plannen
|
||||
- Agent Mode inzetten voor multi-file feature development
|
||||
- Debug Mode gebruiken voor systematisch debuggen
|
||||
- Code review doen met "Find Issues" en tests schrijven
|
||||
- Semantic commits maken en een PR aanmaken
|
||||
- Een app from scratch bouwen met alle 5 technieken
|
||||
|
||||
---
|
||||
|
||||
## Voorbereiding docent
|
||||
- [ ] `npx create-next-app@latest` getest
|
||||
- [ ] 3 `.cursor/rules` bestanden voorbereid
|
||||
- [ ] Plan Mode demo doorgelopen
|
||||
- [ ] Bug voor Debug Mode voorbereid (tag.toUpperCase() in filter)
|
||||
- [ ] GitHub repo klaar voor push demo
|
||||
|
||||
---
|
||||
|
||||
*Laatste update: april 2026*
|
||||
|
||||
Reference in New Issue
Block a user