fix: implement lessons feedback

This commit is contained in:
Tim Rijkse
2026-01-30 11:56:39 +01:00
parent 04f32babd3
commit 1e324e2f0e
18 changed files with 3828 additions and 3199 deletions

View File

@@ -1,320 +1,372 @@
# Les 8: Supabase Basics
# Les 8: Database Principles
---
## Hoofdstuk
**Hoofdstuk 2: Intermediate** (Les 4-9)
**Deel 2: Technical Foundations** (Les 5-9)
## Beschrijving
Leer werken met Supabase: een complete backend-as-a-service met database en authenticatie. Je bouwt je eerste full-stack app met data die persistent is.
Leer de basisprincipes van relationele databases voordat we Supabase gaan gebruiken. Begrijp tabellen, relaties, keys en normalisatie - essentiële kennis voor elke developer.
---
## Te Behandelen
### Wat is Supabase?
### Wat is een Relationele Database?
**Supabase = Database + Auth in één**
- PostgreSQL database (gratis tier: 500MB)
- Ingebouwde authenticatie
- Real-time subscriptions
- File storage
- Auto-generated API
**Een database is:** Een georganiseerde verzameling van data.
**Waarom Supabase voor beginners:**
- Geen eigen server nodig
- Visuele Table Editor (geen SQL kennis nodig)
- Simpele JavaScript SDK
- Gratis tier is ruim voldoende
**Relationeel betekent:** Data is opgeslagen in tabellen die aan elkaar gerelateerd zijn.
**Vergelijk het met Excel:**
- Database = Excel workbook
- Tabel = Excel sheet
- Kolom = Excel kolom (field)
- Rij = Excel rij (record)
---
### Supabase Project Aanmaken
### Tabellen, Kolommen en Rijen
**Stap 1:** Ga naar [supabase.com](https://supabase.com) en maak account
**Voorbeeld: Users tabel**
**Stap 2:** Klik "New Project"
- Naam: `todo-app`
- Database Password: (bewaar deze!)
- Region: `West EU (Frankfurt)` (dichtst bij NL)
| 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 |
**Stap 3:** Wacht ~2 minuten tot project klaar is
**Stap 4:** Ga naar Settings → API en kopieer:
- `Project URL`
- `anon public` key
**Terminologie:**
- **Tabel:** users
- **Kolommen:** id, name, email, created_at
- **Rijen:** 3 records (Tim, Anna, Jan)
- **Cell:** Eén specifieke waarde (bijv. "tim@email.com")
---
### Tabel Maken via Table Editor
### Data Types
**In Supabase Dashboard:**
Elke kolom heeft een type:
1. Ga naar "Table Editor"
2. Klik "New Table"
3. Naam: `todos`
4. Kolommen:
| 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-... |
| Name | Type | Default | Primary |
|------|------|---------|---------|
| id | int8 | - | ✓ (auto) |
| title | text | - | |
| completed | bool | false | |
| created_at | timestamptz | now() | |
5. Klik "Save"
**Test:** Voeg een paar rijen toe via de UI om te zien dat het werkt.
**Kies het juiste type:**
- Prijs? → `decimal` (niet `integer`, want centen)
- Is actief? → `boolean`
- Naam? → `text`
- Aantal? → `integer`
---
### Environment Variables
### Primary Keys
**Wat zijn environment variables?**
- Configuratie die NIET in je code hoort
- API keys, database URLs, secrets
- Verschillend per omgeving (lokaal vs productie)
**Wat:** Een kolom die elke rij UNIEK identificeert.
**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
**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
```
**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
**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
```
**Check `.gitignore` bevat:**
**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!
```
.env*.local
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)
```
---
### Supabase SDK Installeren
### One-to-Many Voorbeeld
```bash
npm install @supabase/supabase-js
```
users posts
------ ------
id | name id | title | user_id
1 | Tim ←────────── 1 | "Blog 1" | 1
2 | Anna ←────┬───── 2 | "Blog 2" | 1
└───── 3 | "Tips" | 2
```
**Maak `src/lib/supabase.ts`:**
```typescript
import { createClient } from '@supabase/supabase-js'
**Lees:** Tim heeft 2 posts, Anna heeft 1 post.
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
---
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
### 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
```
---
### CRUD Operaties
### Database Schema Tekenen
**C - Create (toevoegen):**
```typescript
const { data, error } = await supabase
.from('todos')
.insert({ title: 'Nieuwe taak' })
**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 │
└──────────────┘
```
**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/**`
---
### Lokaal vs Productie
| Aspect | Lokaal | Productie |
|--------|--------|-----------|
| URL | `localhost:3000` | `jouw-app.vercel.app` |
| Env vars | `.env.local` | Vercel Dashboard |
| Database | Supabase (zelfde) | Supabase (zelfde) |
| Command | `npm run dev` | Automatisch via Vercel |
**Let op:** Je gebruikt dezelfde Supabase database voor lokaal en productie. Voor echte projecten zou je aparte databases hebben.
PK = Primary Key
FK = Foreign Key
Pijl = Relatie richting
---
## Tools
- Supabase
- Next.js
- OpenCode/WebStorm
- Vercel
- Git
- Pen en papier / Excalidraw / draw.io
- Supabase Table Editor (vooruitblik)
---
## Lesopdracht (2 uur)
### Bouw een Todo App met Supabase
### Database Design Oefening
**Deel 1: Supabase Setup (30 min)**
**Deel 1: Blog Database Ontwerpen (45 min)**
1. Maak Supabase account en project
2. Maak `todos` tabel via Table Editor
3. Kopieer credentials
4. Installeer `@supabase/supabase-js`
5. Maak `src/lib/supabase.ts`
6. Configureer `.env.local`
Ontwerp een database voor een blog met:
- Users (kunnen posts schrijven)
- Posts (hebben een auteur)
- Comments (op posts, door users)
Test: `npm run dev` werkt zonder errors
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: CRUD Interface (1 uur)**
**Deel 2: Normalisatie Oefening (30 min)**
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
Gegeven deze "slechte" tabel:
Gebruik AI hulp voor de components!
```
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
```
**Deel 3: Authenticatie (30 min)**
Normaliseer naar aparte tabellen:
1. Welke entiteiten zie je?
2. Maak aparte tabellen
3. Voeg relaties toe
1. Installeer auth packages
2. Maak login pagina met Auth UI
3. Toon alleen app voor ingelogde users
4. Test: login met magic link
**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
- Werkende Todo app lokaal
- GitHub repository met code
- Screenshot van werkende app
- Blog database schema (tekening)
- Genormaliseerde library database
- Eindproject database schema
---
## Huiswerk (2 uur)
### Deploy naar Productie + Uitbreiden
### Verdieping en Voorbereiding
**Deel 1: Deployment (30 min)**
**Deel 1: Eindproject Schema Uitwerken (1 uur)**
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!
Werk je database schema volledig uit:
**Deel 2: Features Uitbreiden (1 uur)**
1. **Per tabel:**
- Naam
- Alle kolommen met data types
- Primary key
- Foreign keys
- Defaults
- Nullable fields
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"
2. **Documenteer:**
- Waarom deze structuur?
- Welke relaties?
- Eventuele alternatieve overwegingen
**Deel 3: Polish (30 min)**
**Deel 2: Supabase Account (30 min)**
1. Styling verbeteren met Tailwind
2. Responsive design (mobile friendly)
3. Kleine animaties (fade in/out)
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
- Deployed app op Vercel (werkende URL!)
- Alle features werken in productie
- Screenshot van productie app
- Volledig uitgewerkt database schema voor eindproject
- Supabase account aangemaakt
- Reflectie vragen beantwoord
---
## Leerdoelen
Na deze les kan de student:
- Een Supabase project aanmaken en configureren
- Tabellen maken via de Table Editor (zonder SQL)
- 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
- Het verschil tussen lokale en productie omgeving begrijpen
- 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