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,349 +1,412 @@
# Les 7: Backend Basics met Supabase
> 📋 **Lesmateriaal nog niet uitgewerkt**
>
> De volgende bestanden worden gegenereerd wanneer deze les wordt uitgewerkt:
> - Les07-Slide-Overzicht.md
> - Les07-Lesplan.md
> - Les07-Bijlage-A-Lesopdracht.md
> - Les07-Bijlage-B-Huiswerkopdracht.md
# Les 7: Next.js Fundamentals 2 - API Routes & Data Fetching
---
## Hoofdstuk
**Hoofdstuk 2: Intermediate** (Les 4-9)
**Deel 2: Technical Foundations** (Les 5-9)
## Beschrijving
Zet je eerste "echte" Next.js project op en koppel het aan Supabase voor database en authenticatie. Je leert de complete flow van lokaal ontwikkelen tot productie deployment.
Leer data fetching in Next.js: Server Components, Client Components, API routes en React Query. Bouw een volledig werkende app met data.
---
## Te Behandelen
### Stap 1: Next.js Project Aanmaken
### Server Components vs Client Components
Dit is de eerste keer dat je een volledig Next.js project opzet.
**Server Components (default in Next.js):**
- Renderen op de server
- Geen JavaScript naar de browser
- Kunnen direct data fetchen (async/await)
- Kunnen NIET: useState, useEffect, event handlers
```bash
# Maak nieuw project
npx create-next-app@latest todo-app
**Client Components:**
- Renderen in de browser
- JavaScript naar de browser
- Kunnen interactief zijn
- Markeer met `'use client'` bovenaan
# Beantwoord de vragen:
# ✔ Would you like to use TypeScript? → Yes
# ✔ Would you like to use ESLint? → Yes
# ✔ Would you like to use Tailwind CSS? → Yes
# ✔ Would you like to use `src/` directory? → Yes
# ✔ Would you like to use App Router? → Yes
# ✔ Would you like to customize the default import alias? → No
---
# Ga naar project folder
cd todo-app
# Open in je editor
code . # of: cursor .
```
### Stap 2: Project Structuur Begrijpen
### Wanneer Wat?
```
todo-app/
├── src/
│ └── app/
│ ├── layout.tsx # Root layout (header, footer)
│ ├── page.tsx # Homepage (/)
│ └── globals.css # Global styles + Tailwind
├── public/ # Static files (images, etc.)
├── .env.local # Environment variables (maak zelf aan)
├── next.config.js # Next.js configuratie
├── tailwind.config.ts # Tailwind configuratie
├── package.json # Dependencies
└── tsconfig.json # TypeScript configuratie
Server Component → Data tonen, geen interactie
Client Component → Interactie nodig (forms, buttons, state)
```
### Stap 3: Lokaal Draaien
```bash
# Start development server
npm run dev
# Open browser: http://localhost:3000
```
### Stap 4: Supabase Project Aanmaken
**Op supabase.com:**
1. Ga naar [supabase.com](https://supabase.com) en maak account (gratis)
2. Klik "New Project"
3. Kies een naam (bijv. `todo-app`)
4. Kies een database wachtwoord (bewaar deze!)
5. Kies region: `West EU (Frankfurt)` (dichtst bij NL)
6. Wacht ~2 minuten tot project klaar is
**Credentials ophalen:**
1. Ga naar Settings → API
2. Kopieer:
- `Project URL` → dit wordt `NEXT_PUBLIC_SUPABASE_URL`
- `anon public` key → dit wordt `NEXT_PUBLIC_SUPABASE_ANON_KEY`
### Stap 5: Environment Variables (Lokaal)
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` (deze WEL committen):
```bash
# .env.example (template voor anderen)
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
```
**Check .gitignore:**
```
# .gitignore moet bevatten:
.env*.local
```
### Stap 6: Supabase SDK Installeren
```bash
npm install @supabase/supabase-js
```
### Stap 7: Supabase Client Maken
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)
```
### Stap 8: Database Tabel Maken (via UI)
**In Supabase Dashboard:**
1. Ga naar Table Editor
2. Klik "New Table"
3. Naam: `todos`
4. Kolommen:
| Name | Type | Default | Primary |
|------|------|---------|---------|
| id | int8 | - | ✓ (auto) |
| title | text | - | |
| completed | bool | false | |
| created_at | timestamptz | now() | |
| user_id | uuid | auth.uid() | |
5. Klik "Save"
### Stap 9: CRUD Operaties
```typescript
// CREATE - nieuwe todo toevoegen
const { data, error } = await supabase
.from('todos')
.insert({ title: 'Nieuwe taak' })
// READ - todos ophalen
const { data, error } = await supabase
.from('todos')
.select('*')
.order('created_at', { ascending: false })
// UPDATE - todo afvinken
const { data, error } = await supabase
.from('todos')
.update({ completed: true })
.eq('id', todoId)
// DELETE - todo verwijderen
const { error } = await supabase
.from('todos')
.delete()
.eq('id', todoId)
```
### Stap 10: Authenticatie Setup
```bash
npm install @supabase/auth-ui-react @supabase/auth-ui-shared
```
**Login component:**
**Voorbeeld:**
```tsx
import { Auth } from '@supabase/auth-ui-react'
import { ThemeSupa } from '@supabase/auth-ui-shared'
import { supabase } from '@/lib/supabase'
// Server Component - data ophalen
async function ProductList() {
const products = await fetchProducts() // Direct fetchen!
return <ul>{products.map(p => <li>{p.name}</li>)}</ul>
}
// Client Component - interactie
'use client'
function AddToCartButton({ productId }) {
const [added, setAdded] = useState(false)
return <button onClick={() => setAdded(true)}>Add</button>
}
```
---
### Data Fetching in Server Components
Simpelweg `async/await` gebruiken:
```tsx
// app/products/page.tsx
interface Product {
id: number
name: string
price: number
}
async function getProducts(): Promise<Product[]> {
const res = await fetch('https://api.example.com/products')
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
export function LoginForm() {
return (
<Auth
supabaseClient={supabase}
appearance={{ theme: ThemeSupa }}
providers={[]}
magicLink={true}
/>
<div>
<h1>Producten</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - {product.price}
</li>
))}
</ul>
</div>
)
}
```
**Session checken:**
```typescript
const { data: { user } } = await supabase.auth.getUser()
---
if (user) {
// User is ingelogd
} else {
// Redirect naar login
### API Routes (Route Handlers)
Bouw je eigen API in Next.js:
**Folder structuur:**
```
app/
└── api/
└── products/
└── route.ts → /api/products
```
**GET request (`app/api/products/route.ts`):**
```typescript
import { NextResponse } from 'next/server'
const products = [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Phone', price: 699 },
]
export async function GET() {
return NextResponse.json(products)
}
```
**POST request:**
```typescript
export async function POST(request: Request) {
const body = await request.json()
const newProduct = {
id: Date.now(),
name: body.name,
price: body.price,
}
products.push(newProduct)
return NextResponse.json(newProduct, { status: 201 })
}
```
---
## Deployment naar Vercel (Productie)
### 'use client' Directive
### Stap 1: GitHub Repository
Wanneer je interactie nodig hebt:
```bash
# In je project folder
git init
git add .
git commit -m "Initial commit"
```tsx
'use client' // MOET bovenaan!
# Maak repo op GitHub, dan:
git remote add origin https://github.com/jouw-username/todo-app.git
git push -u origin main
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}
```
### Stap 2: Vercel Deployment
1. Ga naar [vercel.com](https://vercel.com)
2. "Add New Project"
3. Import je GitHub repository
4. **BELANGRIJK:** Voeg Environment Variables toe:
- `NEXT_PUBLIC_SUPABASE_URL` → je Supabase URL
- `NEXT_PUBLIC_SUPABASE_ANON_KEY` → je anon key
5. Klik "Deploy"
### Stap 3: Supabase URL Toestaan
In Supabase Dashboard:
1. Ga naar Authentication → URL Configuration
2. Voeg je Vercel URL toe aan "Redirect URLs":
- `https://jouw-app.vercel.app/**`
---
## Overzicht: Lokaal vs Productie
### React Query (TanStack Query)
| 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 |
**Waarom React Query?**
- Automatische caching
- Loading en error states
- Refetching (focus, interval)
- Optimistic updates
**Let op:** Je gebruikt dezelfde Supabase database voor lokaal en productie. Voor een echt project zou je aparte databases hebben, maar voor deze cursus is dat niet nodig.
**Installatie:**
```bash
npm install @tanstack/react-query
```
**Setup Provider (`app/providers.tsx`):**
```tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
```
**In Layout:**
```tsx
import { Providers } from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
```
---
### useQuery - Data Ophalen
```tsx
'use client'
import { useQuery } from '@tanstack/react-query'
interface Product {
id: number
name: string
price: number
}
async function fetchProducts(): Promise<Product[]> {
const res = await fetch('/api/products')
return res.json()
}
export function ProductList() {
const { data, isLoading, error } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
})
if (isLoading) return <div>Laden...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data?.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
)
}
```
---
### useMutation - Data Wijzigen
```tsx
'use client'
import { useMutation, useQueryClient } from '@tanstack/react-query'
interface NewProduct {
name: string
price: number
}
async function createProduct(product: NewProduct) {
const res = await fetch('/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(product),
})
return res.json()
}
export function AddProductForm() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: createProduct,
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['products'] })
},
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
mutation.mutate({ name: 'New Product', price: 99 })
}
return (
<form onSubmit={handleSubmit}>
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Toevoegen...' : 'Voeg toe'}
</button>
</form>
)
}
```
---
### Combineren: Server + Client
```tsx
// app/products/page.tsx (Server Component)
import { ProductList } from './product-list'
import { AddProductForm } from './add-product-form'
export default function ProductsPage() {
return (
<div>
<h1>Producten</h1>
<AddProductForm /> {/* Client Component */}
<ProductList /> {/* Client Component */}
</div>
)
}
```
---
## Tools
- Next.js
- Supabase (gratis tier)
- Vercel (gratis tier)
- Cursor/OpenCode
- Next.js 14
- React Query (TanStack Query)
- TypeScript
- OpenCode/WebStorm
---
## Lesopdracht (2 uur)
### Bouw een Todo App met Supabase
### Bouw CRUD App met API Routes
**Deel 1: Project Setup (30 min)**
- Run `npx create-next-app@latest todo-app` met juiste opties
- Maak Supabase account en project aan
- Configureer `.env.local` met credentials
- Installeer `@supabase/supabase-js`
- Maak `src/lib/supabase.ts`
- Test: `npm run dev` werkt zonder errors
**Deel 1: API Routes (40 min)**
**Deel 2: Database (20 min)**
- Maak `todos` tabel via Supabase Table Editor
- Voeg 3 test todos toe via de UI
- Test: data is zichtbaar in Table Editor
1. Maak `app/api/products/route.ts`
2. Implementeer GET (alle producten)
3. Implementeer POST (product toevoegen)
4. Test met browser/Postman: `/api/products`
**Deel 3: CRUD Interface (50 min)**
- Bouw UI om todos te tonen (lijst)
- Voeg form toe om nieuwe todo te maken
- Voeg checkbox toe om todo af te vinken
- Voeg delete button toe
- Test: alle CRUD operaties werken
**Deel 2: React Query Setup (20 min)**
**Deel 4: Authenticatie (20 min)**
- Installeer auth packages
- Maak login pagina met Auth UI
- Toon alleen todos voor ingelogde user
- Test: login met magic link werkt
1. Installeer `@tanstack/react-query`
2. Maak `app/providers.tsx`
3. Wrap app in `QueryClientProvider`
**Deel 3: Data Tonen met useQuery (30 min)**
1. Maak `ProductList` Client Component
2. Gebruik `useQuery` om data te fetchen
3. Toon loading state
4. Toon error state
5. Render product lijst
**Deel 4: Data Toevoegen met useMutation (30 min)**
1. Maak `AddProductForm` Client Component
2. Gebruik `useMutation` voor POST
3. Invalidate query na success
4. Toon "Adding..." state
### Deliverable
- Werkende Todo app lokaal
- GitHub repository met code
- Screenshot van werkende app
- Werkende API routes (GET, POST)
- ProductList met useQuery
- AddProductForm met useMutation
- Loading en error states
---
## Huiswerk (2 uur)
### Deploy naar Productie + Uitbreiden
### Volledige CRUD Interface
**Deel 1: Deployment (30 min)**
- Push code naar GitHub
- Deploy naar Vercel
- Configureer environment variables in Vercel
- Voeg Vercel URL toe aan Supabase Redirect URLs
- Test: app werkt op productie URL
**Deel 1: PUT en DELETE Routes (45 min)**
**Deel 2: Features Uitbreiden (1 uur)**
- Filter buttons: Alle / Actief / Voltooid
- Sorteer op datum (nieuwste eerst)
- Toon alleen todos van ingelogde user (filter op `user_id`)
- Loading state tijdens data ophalen
- Error state bij problemen
- Empty state: "Geen todos gevonden"
1. Maak `app/api/products/[id]/route.ts`
2. Implementeer PUT (update product)
3. Implementeer DELETE (verwijder product)
4. Test beide endpoints
**Deel 3: Polish (30 min)**
- Styling verbeteren met Tailwind
- Responsive design (mobile friendly)
- Kleine animaties (fade in/out)
**Deel 2: Update Functionaliteit (45 min)**
1. Maak edit form in ProductList
2. Gebruik useMutation voor PUT
3. Inline editing OF modal
4. Invalidate query na success
**Deel 3: Delete Functionaliteit (30 min)**
1. Voeg delete button toe per product
2. Gebruik useMutation voor DELETE
3. Voeg confirmation dialog toe
4. Invalidate query na success
**Bonus:** Optimistic Updates
- Product direct uit UI verwijderen
- Rollback als server faalt
### Deliverable
- Deployed app op Vercel (werkende URL)
- Alle features werken in productie
- Screenshot van productie app
- Complete CRUD API (GET, POST, PUT, DELETE)
- UI voor alle operaties
- Error handling
- Optimistic updates (bonus)
---
## Leerdoelen
Na deze les kan de student:
- Een Next.js project opzetten met `npx create-next-app`
- De project structuur begrijpen en navigeren
- Een Supabase project aanmaken en configureren
- Environment variables correct beheren (lokaal en productie)
- De Supabase client installeren en configureren
- Tabellen maken via de Supabase UI
- 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 wanneer Server vs Client Components
- De 'use client' directive correct gebruiken
- Data fetchen in Server Components met async/await
- API routes maken met Route Handlers
- GET en POST requests implementeren
- React Query installeren en configureren
- useQuery gebruiken voor data fetching
- useMutation gebruiken voor data mutations
- Loading en error states afhandelen
- Query invalidation toepassen