fix: les 6

This commit is contained in:
2026-03-11 14:07:00 +01:00
parent d5066021ab
commit 9ffdecf2c4
117 changed files with 13198 additions and 5194 deletions

View File

@@ -1,234 +1,58 @@
# Les 5: TypeScript voor React
# Les 5: Next.js — Het React Framework (Part 1)
---
## Hoofdstuk
**Deel 2: Technical Foundations** (Les 4-9)
**Deel 2: Technical Foundations** (Les 4-8)
## Beschrijving
Verdieping in TypeScript met focus op React-patronen. Studenten leren generics, utility types, en hoe je React components, hooks, events en API calls correct typt. Voorbereiding op Les 6 waar ze met Next.js aan de slag gaan.
**Voorkennis:** Les 4 (TypeScript Fundamentals) — basic types, interfaces, union types, type aliases, functies typen.
Introductie Next.js voor React developers. App Router, routing, server/client components, data fetching. Hands-on: QuickPoll app Part 1 (stap 0-3) klassikaal bouwen.
---
## Te Behandelen
## Te Behandelen (~45 min theorie)
### Generics (20 min)
- Waarom generics? Herbruikbare, type-safe code
- `Array<T>`, `Promise<T>` — generics die ze al kennen
- Eigen generics schrijven: `function getFirst<T>(items: T[]): T`
- Generics met constraints: `<T extends { id: string }>`
- `keyof` operator: `function getValue<T, K extends keyof T>(obj: T, key: K): T[K]`
```typescript
// Generic functie
function wrapInArray<T>(value: T): T[] {
return [value];
}
wrapInArray("hello"); // string[]
wrapInArray(42); // number[]
// Met constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
```
- Waarom Next.js? React is een library, Next.js is het framework
- Create-next-app setup met TypeScript + Tailwind
- App Router: folder-based routing (page.tsx = route)
- Layouts: root layout, nested layouts
- Dynamic Routes: [id] met Promise-based params (Next.js 15)
- Server Components vs Client Components
- "use client" directive
- Data Fetching in async Server Components
- Server Actions introductie ("use server")
- Route Groups ((marketing))
- Project structuur best practices
---
### Utility Types (15 min)
- `Partial<T>` — alle properties optioneel (handig voor updates)
- `Pick<T, K>` — selecteer specifieke properties
- `Omit<T, K>` — alles behalve specifieke properties
- `Record<K, V>` — key-value mapping
- Praktisch voorbeeld: `updateUser(id: string, data: Partial<User>)`
## Lesopdracht (120 min, klassikaal)
```typescript
interface User {
id: string;
name: string;
email: string;
age: number;
}
### QuickPoll App Part 1 — samen met Tim
// Partial: voor update functies
function updateUser(id: string, updates: Partial<User>): User { ... }
updateUser("1", { name: "Tim" }); // alleen name updaten
- **Stap 0:** Setup (create-next-app, npm install, dev server)
- **Stap 1:** Layout met navigatie (Tailwind styling)
- **Stap 2:** Homepage met polls lijst (server component)
- **Stap 3:** API route GET single poll (dynamic route, 404 handling)
// Omit: voor create functies (id wordt server-side gegenereerd)
type CreateUserInput = Omit<User, "id">;
// Pick: voor specifieke views
type UserPreview = Pick<User, "id" | "name">;
```
---
### React Props Typen (20 min)
- Interface voor component props
- Children typen met `React.ReactNode`
- Callback props: `onClick: () => void`, `onChange: (value: string) => void`
- Spread props en prop forwarding
- Default values met destructuring
```typescript
interface CardProps {
title: string;
children: React.ReactNode;
variant?: "default" | "highlighted";
onClose?: () => void;
}
function Card({ title, children, variant = "default", onClose }: CardProps) {
return (
<div className={`card card-${variant}`}>
<h2>{title}</h2>
{onClose && <button onClick={onClose}>×</button>}
{children}
</div>
);
}
```
---
### useState & useEffect Typen (15 min)
- Type inference bij useState: `useState(0)` → number
- Explicit types: `useState<User | null>(null)`
- Arrays: `useState<Product[]>([])`
- useEffect met async patterns
```typescript
const [user, setUser] = useState<User | null>(null);
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(false); // inference: boolean
useEffect(() => {
async function fetchData() {
setLoading(true);
const response = await fetch("/api/users");
const data: User[] = await response.json();
setUsers(data);
setLoading(false);
}
fetchData();
}, []);
```
---
### Event Handlers Typen (10 min)
- `React.ChangeEvent<HTMLInputElement>`
- `React.FormEvent<HTMLFormElement>`
- `React.MouseEvent<HTMLButtonElement>`
- Tip: hover in Cursor om het juiste event type te vinden
```typescript
function SearchForm() {
const [query, setQuery] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("Searching:", query);
};
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={handleChange} />
</form>
);
}
```
---
### API Responses & Async Typen (15 min)
- `Promise<T>` voor async functies
- API response types definiëren
- Error handling met types
- Fetch wrapper met generics
```typescript
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
}
// Gebruik
const { data: users } = await fetchApi<User[]>("/api/users");
const { data: product } = await fetchApi<Product>("/api/products/1");
```
Huiswerk: Stap 0-3 zelfstandig afmaken
---
## Tools
- Cursor (Student Plan)
- Next.js 15
- Cursor
- TypeScript
- React (via CDN of Vite)
---
## Lesopdracht (75 min)
### Typed React Dashboard
Studenten bouwen een kleine React-app met volledig getypte components:
**Opdracht:** Bouw een Product Dashboard met:
1. **`ProductCard` component** — props: Product interface, onAddToCart callback
2. **`ProductList` component** — props: Product[], filterCategory (union type)
3. **`SearchBar` component** — props: query string, onChange handler (getypt event)
4. **`useProducts` custom hook** — fetch products, return `{ products, loading, error }`
5. **Alle types in een apart `types.ts` bestand**
**Vereisten:**
- Geen `any` toegestaan
- Alle event handlers correct getypt
- useState met expliciete types waar nodig
- Minstens 1 generic functie (bijv. een `sortBy<T>` of `filterBy<T>`)
---
## Huiswerk (2 uur)
### Extend het Dashboard
Bouw voort op de lesopdracht:
1. **Shopping Cart** toevoegen met getypte state (`CartItem[]`)
2. **API simulatie** — maak een `fetchProducts()` functie met `Promise<Product[]>`
3. **Utility types gebruiken**`Partial<Product>` voor updates, `Omit<Product, "id">` voor create
4. **Bonus: Generic `DataTable<T>` component** — werkt met elke array van objecten
### Deliverable
- Werkend React project met TypeScript
- Alle components volledig getypt
- `npm run check` = 0 errors
- Tailwind CSS
---
## Leerdoelen
Na deze les kan de student:
- Generics schrijven en toepassen
- Utility types gebruiken (Partial, Pick, Omit, Record)
- React component props correct typen
- useState en useEffect met types gebruiken
- Event handlers typen (ChangeEvent, FormEvent, MouseEvent)
- Async functies en API responses typen met Promise<T>
- Een custom hook schrijven met correcte return types
- Uitleggen wat Next.js toevoegt aan React
- Een Next.js project opzetten met App Router
- Verschil tussen Server en Client Components
- File-based routing gebruiken
- Dynamic routes met parameters maken
- Data fetchen in Server Components

View File

@@ -1,365 +1,59 @@
# Les 6: Next.js Fundamentals 1 - SSR & Routing
# Les 6: Next.js — QuickPoll Vervolg (Part 2)
---
## Hoofdstuk
**Deel 2: Technical Foundations** (Les 5-9)
**Deel 2: Technical Foundations** (Les 4-8)
## Beschrijving
Introductie tot Next.js voor React developers. Leer wat Server-Side Rendering is, hoe de App Router werkt, en bouw je eerste Next.js applicatie met meerdere pagina's.
Vervolg Next.js: API Routes, Middleware, Deployment. Hands-on: QuickPoll app Part 2 (stap 4-7) klassikaal afbouwen en deployen.
---
## Te Behandelen
## Te Behandelen (~30-40 min)
### Wat is Next.js?
**React met superpowers:**
- React = library voor UI components
- Next.js = framework dat React complete maakt
**Next.js voegt toe:**
- Routing (geen extra library nodig)
- Server-Side Rendering (SEO, performance)
- API routes (backend in je frontend project)
- Optimalisaties (images, fonts, bundling)
- Recap Les 5 + Q&A
- API Route Handlers dieper: GET, POST met NextResponse, request body parsen
- Middleware: src/middleware.ts, matcher config, use cases
- Environment Variables: .env.local, NEXT_PUBLIC_ prefix
- Loading, Error, Not-Found special files
- next/image, next/link optimalisaties
- Metadata type voor SEO
- Deployment op Vercel (git push → auto deploy)
- Cursor/AI workflow: .cursorrules, Cmd+K, Cmd+L
---
### Server-Side Rendering (SSR) vs Client-Side Rendering (CSR)
## Lesopdracht (120 min, klassikaal)
**Client-Side Rendering (gewone React):**
```
1. Browser vraagt pagina
2. Server stuurt lege HTML + JavaScript
3. JavaScript laadt in browser
4. JavaScript rendert de pagina
5. Gebruiker ziet content
```
**Probleem:** Lege pagina tot JS klaar is. Google ziet ook lege pagina.
### QuickPoll App Part 2 — samen met Tim
**Server-Side Rendering (Next.js):**
```
1. Browser vraagt pagina
2. Server rendert HTML met content
3. Browser toont direct de content
4. JavaScript laadt (hydration)
5. Pagina wordt interactief
```
**Voordeel:** Direct content zichtbaar, beter voor SEO.
- **Stap 4:** API POST vote route (validation, votePoll)
- **Stap 5:** Poll detail pagina (server component met data)
- **Stap 6:** VoteForm (client component, fetch, results display)
- **Stap 7:** Loading, error, not-found states
- **Bonus:** Create poll pagina
- **Deploy naar Vercel**
---
### De App Router
Next.js 13+ gebruikt de "App Router" met file-based routing:
```
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── products/
│ ├── page.tsx → /products
│ └── [id]/
│ └── page.tsx → /products/123
└── layout.tsx → Wrapper voor alle pagina's
```
**De regel:** Elke `page.tsx` wordt een route!
---
### Project Aanmaken
```bash
npx create-next-app@latest mijn-app
# Antwoorden:
# ✔ TypeScript? → Yes
# ✔ ESLint? → Yes
# ✔ Tailwind CSS? → Yes
# ✔ `src/` directory? → Yes
# ✔ App Router? → Yes
# ✔ Customize import alias? → No
cd mijn-app
npm run dev
```
Open `http://localhost:3000` - je app draait!
---
### Pagina's Maken
**Simpele pagina (`app/about/page.tsx`):**
```tsx
export default function AboutPage() {
return (
<div className="p-8">
<h1 className="text-3xl font-bold">Over Ons</h1>
<p>Welkom op de about pagina!</p>
</div>
)
}
```
Dat is alles! Ga naar `/about` en je ziet je pagina.
---
### Layouts
Layouts wrappen pagina's en blijven behouden tijdens navigatie.
**Root Layout (`app/layout.tsx`):**
```tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="nl">
<body>
<header className="p-4 bg-blue-500 text-white">
<nav>Mijn App</nav>
</header>
<main>{children}</main>
<footer className="p-4 bg-gray-200">
© 2024
</footer>
</body>
</html>
)
}
```
---
### Nested Layouts
Je kunt layouts nesten voor secties:
```
app/
├── layout.tsx → Root layout
├── page.tsx → Homepage
└── dashboard/
├── layout.tsx → Dashboard layout (sidebar)
└── page.tsx → Dashboard pagina
```
**Dashboard Layout (`app/dashboard/layout.tsx`):**
```tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex">
<aside className="w-64 bg-gray-100 p-4">
<nav>Sidebar hier</nav>
</aside>
<div className="flex-1">{children}</div>
</div>
)
}
```
---
### Dynamic Routes
Voor pagina's met parameters zoals `/products/123`:
**Folder structuur:**
```
app/products/[id]/page.tsx
```
**De pagina:**
```tsx
interface Props {
params: { id: string }
}
export default function ProductPage({ params }: Props) {
return (
<div>
<h1>Product {params.id}</h1>
</div>
)
}
```
Nu werkt `/products/1`, `/products/abc`, etc.
---
### Link Component
Gebruik `Link` voor client-side navigatie (snel, geen page reload):
```tsx
import Link from 'next/link'
export default function Navigation() {
return (
<nav className="flex gap-4">
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/products">Products</Link>
<Link href="/products/123">Product 123</Link>
</nav>
)
}
```
**Niet doen:**
```tsx
// ❌ Dit werkt maar is langzamer
<a href="/about">About</a>
```
---
### Special Files
Next.js heeft speciale bestanden:
| File | Doel |
|------|------|
| `page.tsx` | De pagina content |
| `layout.tsx` | Wrapper, blijft behouden |
| `loading.tsx` | Loading state |
| `error.tsx` | Error boundary |
| `not-found.tsx` | 404 pagina |
**Loading state (`app/products/loading.tsx`):**
```tsx
export default function Loading() {
return <div>Laden...</div>
}
```
---
### Metadata (SEO)
Voeg metadata toe per pagina:
```tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Over Ons | Mijn App',
description: 'Lees meer over ons bedrijf',
}
export default function AboutPage() {
return <h1>Over Ons</h1>
}
```
Huiswerk: App afmaken, deployen op Vercel, bonus features
---
## Tools
- Next.js 14
- Next.js 15
- Cursor
- TypeScript
- Tailwind CSS
- OpenCode/WebStorm
- Vercel
---
## Lesopdracht (2 uur)
### Bouw Multi-Page Next.js App
**Deel 1: Project Setup (20 min)**
1. `npx create-next-app@latest my-shop` (TypeScript + Tailwind + App Router)
2. Open in editor
3. `npm run dev`
4. Verifieer: `localhost:3000` werkt
**Deel 2: Pagina's Maken (40 min)**
Maak deze pagina's:
1. `/` - Homepage met welkomstbericht
2. `/about` - Over ons pagina
3. `/products` - Producten overzicht
4. `/contact` - Contact pagina
Elke pagina moet unieke content hebben.
**Deel 3: Layout met Navigatie (30 min)**
1. Maak Header component met navigatie (gebruik Link)
2. Maak Footer component
3. Voeg beide toe aan root layout
4. Test: navigatie werkt zonder page reload
**Deel 4: Dynamic Route (30 min)**
1. Maak `/products/[id]` route
2. Toon product ID op de pagina
3. Maak links naar `/products/1`, `/products/2`, `/products/3`
4. Test: alle links werken
**Bonus:** Voeg `loading.tsx` toe aan products folder
### Deliverable
- Werkende Next.js app met 4+ pagina's
- Navigatie met Link component
- Dynamic route die werkt
- Deploy naar Vercel
---
## Huiswerk (2 uur)
### Uitbreiden en Verdiepen
**Deel 1: Nested Layout (45 min)**
1. Maak `/dashboard` section met eigen layout
2. Dashboard layout heeft sidebar
3. Maak `/dashboard/settings` en `/dashboard/profile` pagina's
4. Test: sidebar blijft bij navigatie binnen dashboard
**Deel 2: Special Files (45 min)**
1. Maak `loading.tsx` voor products (toon "Laden...")
2. Maak `error.tsx` voor products (toon error message)
3. Maak `not-found.tsx` in app root (custom 404)
4. Test elk van deze states
**Deel 3: Metadata (30 min)**
1. Voeg metadata toe aan elke pagina (title, description)
2. Bekijk in browser: `<head>` toont juiste meta tags
3. Test met Lighthouse: SEO score
### Deliverable
- App met nested layouts
- Special files (loading, error, not-found)
- Metadata op elke pagina
- Screenshot van Lighthouse SEO score
---
## Leerdoelen
Na deze les kan de student:
- Uitleggen wat Next.js toevoegt aan React
- Het verschil tussen SSR en CSR beschrijven
- Een Next.js project opzetten met App Router
- Pagina's maken met file-based routing
- Layouts gebruiken voor herhalende elementen
- Dynamic routes maken met parameters
- Link component gebruiken voor navigatie
- Special files toepassen (loading, error, not-found)
- Metadata toevoegen voor SEO
- API Route Handlers schrijven (GET, POST)
- Middleware implementeren
- Environment variables gebruiken
- Special files (loading, error, not-found) toepassen
- Een Next.js app deployen op Vercel
- Cursor/AI workflow toepassen

View File

@@ -1,412 +1,372 @@
# Les 7: Next.js Fundamentals 2 - API Routes & Data Fetching
# Les 7: Database Principles
---
## Hoofdstuk
**Deel 2: Technical Foundations** (Les 5-9)
**Deel 2: Technical Foundations** (Les 4-8)
## Beschrijving
Leer data fetching in Next.js: Server Components, Client Components, API routes en React Query. Bouw een volledig werkende app met data.
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
### Server Components vs Client Components
### Wat is een Relationele Database?
**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
**Een database is:** Een georganiseerde verzameling van data.
**Client Components:**
- Renderen in de browser
- JavaScript naar de browser
- Kunnen interactief zijn
- Markeer met `'use client'` bovenaan
**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)
---
### Wanneer Wat?
### Tabellen, Kolommen en Rijen
```
Server Component → Data tonen, geen interactie
Client Component → Interactie nodig (forms, buttons, state)
```
**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:**
```tsx
// Server Component - data ophalen
async function ProductList() {
const products = await fetchProducts() // Direct fetchen!
return <ul>{products.map(p => <li>{p.name}</li>)}</ul>
}
```
users
------
id (PRIMARY KEY) | name | email
1 | Tim | tim@email.com
2 | Anna | anna@email.com
```
// Client Component - interactie
'use client'
function AddToCartButton({ productId }) {
const [added, setAdded] = useState(false)
return <button onClick={() => setAdded(true)}>Add</button>
}
**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)
```
---
### Data Fetching in Server Components
### One-to-Many Voorbeeld
Simpelweg `async/await` gebruiken:
```
users posts
------ ------
id | name id | title | user_id
1 | Tim ←────────── 1 | "Blog 1" | 1
2 | Anna ←────┬───── 2 | "Blog 2" | 1
└───── 3 | "Tips" | 2
```
```tsx
// app/products/page.tsx
interface Product {
id: number
name: string
price: number
}
**Lees:** Tim heeft 2 posts, Anna heeft 1 post.
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()
### Many-to-Many met Tussentabel
return (
<div>
<h1>Producten</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - {product.price}
</li>
))}
</ul>
</div>
)
}
```
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
```
---
### API Routes (Route Handlers)
### Database Schema Tekenen
Bouw je eigen API in Next.js:
**Tools:** draw.io, Excalidraw, pen en papier
**Folder structuur:**
**Conventie:**
```
app/
└── api/
└── products/
── route.ts → /api/products
┌──────────────┐ ┌──────────────┐
│ users │ │ posts │
├──────────────┤ ├──────────────┤
│ id (PK) ───┐ │ id (PK) │
│ name │ │ │ title │
│ email │ └────→│ user_id (FK) │
│ created_at │ │ content │
└──────────────┘ │ created_at │
└──────────────┘
```
**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 })
}
```
---
### 'use client' Directive
Wanneer je interactie nodig hebt:
```tsx
'use client' // MOET bovenaan!
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}
```
---
### React Query (TanStack Query)
**Waarom React Query?**
- Automatische caching
- Loading en error states
- Refetching (focus, interval)
- Optimistic updates
**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>
)
}
```
PK = Primary Key
FK = Foreign Key
Pijl = Relatie richting
---
## Tools
- Next.js 14
- React Query (TanStack Query)
- TypeScript
- OpenCode/WebStorm
- Pen en papier / Excalidraw / draw.io
- Supabase Table Editor (vooruitblik)
---
## Lesopdracht (2 uur)
### Bouw CRUD App met API Routes
### Database Design Oefening
**Deel 1: API Routes (40 min)**
**Deel 1: Blog Database Ontwerpen (45 min)**
1. Maak `app/api/products/route.ts`
2. Implementeer GET (alle producten)
3. Implementeer POST (product toevoegen)
4. Test met browser/Postman: `/api/products`
Ontwerp een database voor een blog met:
- Users (kunnen posts schrijven)
- Posts (hebben een auteur)
- Comments (op posts, door users)
**Deel 2: React Query Setup (20 min)**
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
1. Installeer `@tanstack/react-query`
2. Maak `app/providers.tsx`
3. Wrap app in `QueryClientProvider`
**Deel 2: Normalisatie Oefening (30 min)**
**Deel 3: Data Tonen met useQuery (30 min)**
Gegeven deze "slechte" tabel:
1. Maak `ProductList` Client Component
2. Gebruik `useQuery` om data te fetchen
3. Toon loading state
4. Toon error state
5. Render product lijst
```
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 4: Data Toevoegen met useMutation (30 min)**
Normaliseer naar aparte tabellen:
1. Welke entiteiten zie je?
2. Maak aparte tabellen
3. Voeg relaties toe
1. Maak `AddProductForm` Client Component
2. Gebruik `useMutation` voor POST
3. Invalidate query na success
4. Toon "Adding..." state
**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 API routes (GET, POST)
- ProductList met useQuery
- AddProductForm met useMutation
- Loading en error states
- Blog database schema (tekening)
- Genormaliseerde library database
- Eindproject database schema
---
## Huiswerk (2 uur)
### Volledige CRUD Interface
### Verdieping en Voorbereiding
**Deel 1: PUT en DELETE Routes (45 min)**
**Deel 1: Eindproject Schema Uitwerken (1 uur)**
1. Maak `app/api/products/[id]/route.ts`
2. Implementeer PUT (update product)
3. Implementeer DELETE (verwijder product)
4. Test beide endpoints
Werk je database schema volledig uit:
**Deel 2: Update Functionaliteit (45 min)**
1. **Per tabel:**
- Naam
- Alle kolommen met data types
- Primary key
- Foreign keys
- Defaults
- Nullable fields
1. Maak edit form in ProductList
2. Gebruik useMutation voor PUT
3. Inline editing OF modal
4. Invalidate query na success
2. **Documenteer:**
- Waarom deze structuur?
- Welke relaties?
- Eventuele alternatieve overwegingen
**Deel 3: Delete Functionaliteit (30 min)**
**Deel 2: Supabase Account (30 min)**
1. Voeg delete button toe per product
2. Gebruik useMutation voor DELETE
3. Voeg confirmation dialog toe
4. Invalidate query na success
Bereid je voor op volgende les:
1. Maak account op [supabase.com](https://supabase.com)
2. Verken de interface
3. Bekijk de Table Editor
**Bonus:** Optimistic Updates
- Product direct uit UI verwijderen
- Rollback als server faalt
**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
- Complete CRUD API (GET, POST, PUT, DELETE)
- UI voor alle operaties
- Error handling
- Optimistic updates (bonus)
- Volledig uitgewerkt database schema voor eindproject
- Supabase account aangemaakt
- Reflectie vragen beantwoord
---
## Leerdoelen
Na deze les kan de student:
- 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
- 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

View File

@@ -1,372 +1,305 @@
# Les 8: Database Principles
# Les 8: Supabase: Auth & CRUD
---
## Hoofdstuk
**Deel 2: Technical Foundations** (Les 5-9)
**Deel 2: Technical Foundations** (Les 4-8)
## Beschrijving
Leer de basisprincipes van relationele databases voordat we Supabase gaan gebruiken. Begrijp tabellen, relaties, keys en normalisatie - essentiële kennis voor elke developer.
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.
---
## Te Behandelen
### Wat is een Relationele Database?
### Wat is Supabase?
**Een database is:** Een georganiseerde verzameling van data.
**Supabase = Database + Auth in één**
- PostgreSQL database (gratis tier: 500MB)
- Ingebouwde authenticatie
- Real-time subscriptions
- File storage
- Auto-generated API
**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)
**Waarom Supabase voor beginners:**
- Geen eigen server nodig
- Visuele Table Editor (geen SQL kennis nodig)
- Simpele JavaScript SDK
- Gratis tier is ruim voldoende
---
### Tabellen, Kolommen en Rijen
### Supabase Project Aanmaken
**Voorbeeld: Users tabel**
**Stap 1:** Ga naar [supabase.com](https://supabase.com) en maak account
| 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 2:** Klik "New Project"
- Naam: `todo-app`
- Database Password: (bewaar deze!)
- Region: `West EU (Frankfurt)` (dichtst bij NL)
**Terminologie:**
- **Tabel:** users
- **Kolommen:** id, name, email, created_at
- **Rijen:** 3 records (Tim, Anna, Jan)
- **Cell:** Eén specifieke waarde (bijv. "tim@email.com")
**Stap 3:** Wacht ~2 minuten tot project klaar is
**Stap 4:** Ga naar Settings → API en kopieer:
- `Project URL`
- `anon public` key
---
### Data Types
### Je Database Schema Implementeren
Elke kolom heeft een type:
In Les 7 heb je een database schema ontworpen. Nu gaan we dat implementeren!
| 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-... |
**In Supabase Dashboard → Table Editor:**
**Kies het juiste type:**
- Prijs? → `decimal` (niet `integer`, want centen)
- Is actief? → `boolean`
- Naam? → `text`
- Aantal? → `integer`
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() | |
---
### Primary Keys
### Environment Variables
**Wat:** Een kolom die elke rij UNIEK identificeert.
**Wat zijn environment variables?**
- Configuratie die NIET in je code hoort
- API keys, database URLs, secrets
- Verschillend per omgeving (lokaal vs productie)
**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 `.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
```
**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)
**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
```
---
### One-to-Many Voorbeeld
### Supabase SDK Installeren
```
users posts
------ ------
id | name id | title | user_id
1 | Tim ←────────── 1 | "Blog 1" | 1
2 | Anna ←────┬───── 2 | "Blog 2" | 1
└───── 3 | "Tips" | 2
```bash
npm install @supabase/supabase-js
```
**Lees:** Tim heeft 2 posts, Anna heeft 1 post.
**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!
### 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
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
```
---
### Database Schema Tekenen
### CRUD Operaties
**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 │
└──────────────┘
**C - Create (toevoegen):**
```typescript
const { data, error } = await supabase
.from('todos')
.insert({ title: 'Nieuwe taak' })
```
PK = Primary Key
FK = Foreign Key
Pijl = Relatie richting
**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/**`
---
## Tools
- Pen en papier / Excalidraw / draw.io
- Supabase Table Editor (vooruitblik)
- Supabase
- Next.js
- OpenCode/WebStorm
- Vercel
- Git
---
## Lesopdracht (2 uur)
### Database Design Oefening
### Bouw een Todo App met Supabase
**Deel 1: Blog Database Ontwerpen (45 min)**
**Groepsdiscussie (15 min):**
Bespreek klassikaal de database schemas uit Les 7 - wie heeft welke structuur gekozen en waarom?
Ontwerp een database voor een blog met:
- Users (kunnen posts schrijven)
- Posts (hebben een auteur)
- Comments (op posts, door users)
**Deel 1: Supabase Setup (30 min)**
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
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`
**Deel 2: Normalisatie Oefening (30 min)**
Test: `npm run dev` werkt zonder errors
Gegeven deze "slechte" tabel:
**Deel 2: CRUD Interface (1 uur)**
```
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
```
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
Normaliseer naar aparte tabellen:
1. Welke entiteiten zie je?
2. Maak aparte tabellen
3. Voeg relaties toe
Gebruik AI hulp voor de components!
**Deel 3: Eindproject Schema (45 min)**
**Deel 3: Authenticatie (30 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
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
- Blog database schema (tekening)
- Genormaliseerde library database
- Eindproject database schema
- Werkende Todo app lokaal
- GitHub repository met code
- Screenshot van werkende app
---
## Huiswerk (2 uur)
### Verdieping en Voorbereiding
### Deploy naar Productie + Uitbreiden
**Deel 1: Eindproject Schema Uitwerken (1 uur)**
**Deel 1: Deployment (30 min)**
Werk je database schema volledig uit:
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!
1. **Per tabel:**
- Naam
- Alle kolommen met data types
- Primary key
- Foreign keys
- Defaults
- Nullable fields
**Deel 2: Features Uitbreiden (1 uur)**
2. **Documenteer:**
- Waarom deze structuur?
- Welke relaties?
- Eventuele alternatieve overwegingen
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 2: Supabase Account (30 min)**
**Deel 3: Polish (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?
1. Styling verbeteren met Tailwind
2. Responsive design (mobile friendly)
3. Kleine animaties (fade in/out)
### Deliverable
- Volledig uitgewerkt database schema voor eindproject
- Supabase account aangemaakt
- Reflectie vragen beantwoord
- Deployed app op Vercel (werkende URL!)
- Alle features werken in productie
- Screenshot van productie app
---
## 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
- 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

View File

@@ -1,305 +1,126 @@
# Les 9: Supabase Basics
# Les 9: Full-Stack Mini Project
---
## Hoofdstuk
**Deel 2: Technical Foundations** (Les 5-9)
**Deel 3: Integration & AI Tooling** (Les 9-12)
## Beschrijving
Leer werken met Supabase: een complete backend-as-a-service met database en authenticatie. Pas je database schema uit Les 8 toe en bouw je eerste full-stack app.
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.
---
## Te Behandelen
### Wat is Supabase?
### Groepsdiscussie (15 min)
Bespreek klassikaal de Supabase ervaringen uit Les 8 - welke uitdagingen kwamen jullie tegen bij authenticatie en RLS?
**Supabase = Database + Auth in één**
- PostgreSQL database (gratis tier: 500MB)
- Ingebouwde authenticatie
- Real-time subscriptions
- File storage
- Auto-generated API
### Doel van deze les
**Waarom Supabase voor beginners:**
- Geen eigen server nodig
- Visuele Table Editor (geen SQL kennis nodig)
- Simpele JavaScript SDK
- Gratis tier is ruim voldoende
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.
---
### Supabase Project Aanmaken
### Mini Project: Personal Bookmarks
**Stap 1:** Ga naar [supabase.com](https://supabase.com) en maak account
Een simpele bookmark manager waar je links kunt opslaan.
**Stap 2:** Klik "New Project"
- Naam: `todo-app`
- Database Password: (bewaar deze!)
- Region: `West EU (Frankfurt)` (dichtst bij NL)
**Features:**
- Login met Supabase Auth
- Bookmarks toevoegen (URL + titel)
- Bookmarks bekijken en verwijderen
**Stap 3:** Wacht ~2 minuten tot project klaar is
**Stap 4:** Ga naar Settings → API en kopieer:
- `Project URL`
- `anon public` key
**Tech stack:**
- Next.js 14 met App Router
- TypeScript
- Tailwind CSS
- Supabase (auth + database)
- React Query
---
### Je Database Schema Implementeren
### Stap-voor-stap
In Les 8 heb je een database schema ontworpen. Nu gaan we dat implementeren!
#### Database Schema
**In Supabase Dashboard → Table Editor:**
**Tabel: bookmarks**
| Kolom | Type |
|-------|------|
| id | uuid (PK) |
| user_id | uuid (FK naar auth.users) |
| url | text |
| title | text |
| created_at | timestamptz |
1. Klik "New Table"
2. Gebruik je schema uit Les 8
3. Voeg kolommen toe met de juiste types
4. Definieer Primary Keys en Foreign Keys
**RLS:** Users kunnen alleen eigen bookmarks zien, toevoegen en verwijderen.
**Voorbeeld: todos tabel**
#### Wat je bouwt
| 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/**`
1. **Login pagina** - Supabase Auth
2. **Dashboard** - Lijst van bookmarks
3. **Add form** - URL + titel invoeren
4. **Delete** - Bookmark verwijderen
---
## Tools
- Supabase
- Next.js
- OpenCode/WebStorm
- Vercel
- Git
- VS Code
- Supabase Dashboard
- Browser DevTools
---
## Lesopdracht (2 uur)
## Lesopdracht (2.5 uur)
### Bouw een Todo App met Supabase
### Bouw de Bookmark Manager
**Groepsdiscussie (15 min):**
Bespreek klassikaal de database schemas uit Les 8 - wie heeft welke structuur gekozen en waarom?
**Checkpoints:**
**Deel 1: Supabase Setup (30 min)**
| 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 |
1. Maak Supabase account en project
2. Maak je tabellen via Table Editor (gebaseerd op Les 8 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
**Minimale eisen:**
- [ ] Login/logout werkt
- [ ] Bookmarks toevoegen werkt
- [ ] Bookmarks worden getoond
- [ ] Delete werkt
### Deliverable
- Werkende Todo app lokaal
- GitHub repository met code
- Screenshot van werkende app
- Werkende lokale applicatie
- Screenshot van je app met data
---
## Huiswerk (2 uur)
## Huiswerk (1 uur)
### Deploy naar Productie + Uitbreiden
### Reflectie
**Deel 1: Deployment (30 min)**
Schrijf korte reflectie (200 woorden):
- Wat ging goed bij het integreren?
- Waar liep je vast?
- Wat zou je volgende keer anders doen?
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)
Maak een lijst van 3 verbeterpunten voor je code.
### Deliverable
- Deployed app op Vercel (werkende URL!)
- Alle features werken in productie
- Screenshot van productie app
- Reflectie (200 woorden)
- 3 verbeterpunten
---
## 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 8 toepassen in de praktijk
- 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

View File

@@ -1,126 +1,116 @@
# Les 10: Full-Stack Mini Project
# Les 10: Styling: Tailwind CSS & shadcn/ui
---
## Hoofdstuk
**Deel 3: Integration & AI Tooling** (Les 10-12)
**Deel 3: Full-Stack Development** (Les 9-12)
## 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.
Styling je applicatie met Tailwind CSS en modern components met shadcn/ui. Utility-first approach, component libraries, themeing en dark mode implementatie.
---
## Te Behandelen
## Te Behandelen (~45 min)
### Groepsdiscussie (15 min)
Bespreek klassikaal de Supabase ervaringen uit Les 9 - welke uitdagingen kwamen jullie tegen bij authenticatie en RLS?
### Doel van deze les
Je hebt nu alle bouwstenen:
- TypeScript (Les 5)
- Next.js met App Router (Les 6-7)
- Supabase database & auth (Les 8-9)
Vandaag combineren we dit in een **werkende mini-app**. Geen nieuwe concepten - alleen integratie en toepassing.
---
### Mini Project: Personal Bookmarks
Een simpele bookmark manager waar je links kunt opslaan.
**Features:**
- Login met Supabase Auth
- Bookmarks toevoegen (URL + titel)
- Bookmarks bekijken en verwijderen
**Tech stack:**
- Next.js 14 met App Router
- TypeScript
- Tailwind CSS
- Supabase (auth + database)
- React Query
---
### 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
- Waarom Tailwind? Utility-first CSS approach vs traditioneel CSS
- Tailwind in Next.js (meestal al ingesteld)
- Core utilities: spacing, colors, flexbox, grid, responsive (mobile-first)
- Tailwind components: buttons, cards, forms patterns
- Wat is shadcn/ui? Beautifully designed component library
- shadcn/ui installatie en configuratie
- shadcn/ui components: Button, Card, Input, Dialog, etc.
- Custom Tailwind color themes (tailwind.config.ts)
- Dark mode implementation met Tailwind
- Performance: class optimization, purging unused styles
---
## Tools
- VS Code
- Supabase Dashboard
- Browser DevTools
- Tailwind CSS
- shadcn/ui
- Cursor
- TypeScript
---
## Lesopdracht (2.5 uur)
## Lesopdracht (2 uur, klassikaal)
### Bouw de Bookmark Manager
### Styling Je Mini-Project
**Checkpoints:**
**Groepsdiscussie (15 min):**
Bespreek klassikaal de Full-Stack Mini Project ervaringen uit Les 9 - welke onderdelen werkten goed en waar liepen jullie vast?
| 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 |
**Deel 1: Tailwind Basics (30 min)**
**Minimale eisen:**
- [ ] Login/logout werkt
- [ ] Bookmarks toevoegen werkt
- [ ] Bookmarks worden getoond
- [ ] Delete werkt
1. Open je mini-project uit Les 9
2. Refactor bestaande components met Tailwind classes:
- Button: `bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded`
- Card: `bg-white rounded-lg shadow-lg p-6`
- Form inputs: `w-full px-3 py-2 border border-gray-300 rounded-md`
3. Voeg spacing, colors, responsive design toe
**Deel 2: shadcn/ui Setup & Components (45 min)**
1. Install shadcn/ui: `npx shadcn-ui@latest init`
2. Install components: `npx shadcn-ui@latest add button input card dialog`
3. Replace je custom components met shadcn versions
4. Test styling en interactiviteit
**Deel 3: Theme & Dark Mode (30 min)**
1. Customize Tailwind color scheme in `tailwind.config.ts`
2. Voeg dark mode toggle toe (localStorage + CSS class toggle)
3. Zorg dat je hele app responsive is (mobile-first)
### Deliverable
- Werkende lokale applicatie
- Screenshot van je app met data
- Gerestyled mini-project met Tailwind + shadcn/ui
- Dark mode toggle werkend
- Mobile responsive design
- GitHub commit met improvements
---
## Huiswerk (1 uur)
## Huiswerk (2 uur)
### Reflectie
### Vervolg Styling & Polish
Schrijf korte reflectie (200 woorden):
- Wat ging goed bij het integreren?
- Waar liep je vast?
- Wat zou je volgende keer anders doen?
**Deel 1: Component Library Uitbreiden (1 uur)**
Maak een lijst van 3 verbeterpunten voor je code.
Install en integreer meer shadcn/ui components:
- Select, Tabs, Modal/Dialog
- Forms package voor betere form handling
- Toast notifications
- Zorg dat je hele app consistent gelayout is
**Deel 2: Custom Theme (30 min)**
Maak je eigen color palette in `tailwind.config.ts`:
- Primary, secondary, accent colors
- Custom spacing, typography
- Test in light en dark mode
**Deel 3: Accessibility & Polish (30 min)**
1. Voeg alt text toe aan images
2. Zorg voor proper heading hierarchy
3. Test keyboard navigation
4. Fix UI inconsistencies
### Deliverable
- Reflectie (200 woorden)
- 3 verbeterpunten
- Compleet gestylde mini-project
- Alle shadcn/ui components correct geintegreerd
- Custom color theme
- GitHub commits met styling improvements
---
## 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
- Tailwind utility-first approach begrijpen en toepassen
- shadcn/ui components installeren en gebruiken
- Custom Tailwind themes maken
- Dark mode implementeren
- Responsive design (mobile-first) toepassen
- Professional-looking UI bouwen met componenten
- Het verschil tussen styling approaches begrijpen

View File

@@ -1,237 +1,233 @@
# Les 11: Hands-on: Van Idee naar Prototype
# Les 11: Van Idee naar Prototype
---
## Hoofdstuk
**Deel 3: AI Tooling & Prototyping** (Les 10-12)
**Deel 3: Full-Stack Development** (Les 9-12)
## Beschrijving
Pas alles wat je hebt geleerd toe in een hands-on sessie. Ga van een vaag idee naar een werkend prototype met behulp van je AI workflow.
Proces van vaag idee naar werkend prototype. Feature breakdown, component thinking, MVP planning, user stories, en voorbereiding op eindproject. Kiezen van eindproject idea en architectuur opzetten.
---
## Te Behandelen
## Te Behandelen (~45 min)
- Hoe ga je van vaag idee naar concrete features?
- Feature breakdown method en AI-hulp
- Component thinking: UI opdelen in herbruikbare stukken
- MVP (Minimum Viable Product) denken
- User stories schrijven (As a user I want to...)
- Prioritizing features: must-have, nice-to-have, later
- Database schema planning voor je idee
- Project architecture design
- Folder structure planning
- Eindproject idee kiezen (vereenvoudigde versie)
---
### Van Idee naar Feature Breakdown
**Het probleem:** Je hebt een idee, maar waar begin je?
**Stap 1: Beschrijf je idee in 1-2 zinnen**
**Stap 1: Beschrijf je idee in 1 zin**
```
"Ik wil een app waar je kunt bijhouden welke planten water nodig hebben."
"Ik wil een app waar je recepten kan zoeken op basis van ingrediënten die je hebt"
```
**Stap 2: Vraag AI om feature breakdown**
**Stap 2: Werk uit wat gebruikers willen**
```
Prompt: Ik wil een plant watering tracker app bouwen.
Wat zijn de minimale features voor een werkend prototype?
Denk aan: wat moet een gebruiker kunnen doen?
User stories:
- Als gebruiker wil ik ingrediënten kunnen invoeren
- Als gebruiker wil ik recepten suggesties krijgen
- Als gebruiker wil ik recepten kunnen opslaan
```
**Stap 3: Prioriteer (MVP denken)**
- Wat is essentieel? → In prototype
- Wat is nice-to-have? → Later
**Stap 3: Break down in features**
```
Core Features:
- Ingredient input form
- Recipe search/filter
- Save favorites
- View saved recipes
Nice-to-have:
- Meal planning
- Shopping list generator
- Nutritional info
- Export recipes
```
**Stap 4: Prioriteer (MVP)**
- ✅ Input form + recipe search
- ✅ Display results
- ✅ Save/favorites
- ❌ Shopping list (later)
- ❌ Meal planning (later)
---
### Component Thinking
**Vraag jezelf af:**
- Welke "blokken" zie ik op het scherm?
- Welke blokken worden herhaald?
- Welke blokken komen op meerdere pagina's?
**Voorbeeld: Plant Tracker**
**Visualiseer je app:**
```
Herhaalde componenten:
- PlantCard (naam, foto, laatste water datum)
- WaterButton (markeer als water gegeven)
Pagina componenten:
- PlantList (toont alle PlantCards)
- AddPlantForm (nieuw plant toevoegen)
App
├── Header
├── SearchForm (input + submit)
├── RecipeList
│ └── RecipeCard (repeated)
│ ├── Title
│ ├── Ingredients
│ ├── Favorite button
│ └── View details
└── Footer
```
**Questions to ask:**
- Welke componenten worden herhaald?
- Welke componenten zijn child components?
- Welke componenten hebben state?
- Waar gaat data vandaan?
---
### MVP (Minimum Viable Product) Denken
### MVP Denken
**Wat is MVP?**
De simpelste versie van je app die nog steeds waarde levert.
**MVP = Minimum Viable Product**
Het eenvoudigst mogelijke product dat nog steeds waarde levert.
**❌ Niet MVP:**
- Alle features tegelijk
- Perfect design
- Edge cases afhandelen
- Login systeem
- Perfect design met animaties
- Geavanceerde filters
- Social features
**✅ Wel MVP:**
- Core functionaliteit werkt
- Basis styling
- Basis styling (Tailwind)
- Happy path werkt
- Hardcoded data is oké
- Data persists (database)
---
### De Prototype Workflow
### Database Planning
**Voor recipe app:**
```
1. IDEE (1-2 zinnen)
2. FEATURES (AI breakdown)
3. PRIORITEER (wat is MVP?)
4. COMPONENTS (welke blokken?)
5. BOUWEN (tool per stap)
6. ITEREREN (feedback → aanpassen)
users table:
- id, email, password_hash, created_at
recipes table:
- id, title, ingredients, instructions, created_at
favorites table:
- id, user_id, recipe_id, created_at
```
---
### Voorbeeld: Weer Widget Prototype
**Stap 1: Idee**
"Simpele weer widget met 3-daagse forecast"
**Stap 2: AI Feature Breakdown**
```
Vraag aan ChatGPT:
"Wat zijn de minimale features voor een weer widget met 3-daagse forecast?"
Antwoord:
- Huidige temperatuur tonen
- Weer icoon (zon, regen, etc.)
- 3-daagse forecast (dag + temp + icoon)
- Locatie tonen
```
**Stap 3: MVP Selectie**
- ✅ Huidige temperatuur
- ✅ Weer icoon
- ✅ 3 dagen forecast
- ❌ Locatie selectie (later)
- ❌ Animated icons (later)
**Stap 4: Components**
```
WeatherWidget/
├── CurrentWeather (temp + icoon)
├── ForecastDay (dag + temp + icoon)
└── ForecastList (3x ForecastDay)
```
**Stap 5: Bouwen**
1. v0.dev: "Weather widget with current temp and 3 day forecast, minimal design"
2. OpenCode: "Integreer dit in mijn project, maak components in src/components/weather/"
**Stap 6: Itereren**
- Wat werkt niet?
- Wat kan beter?
- Vraag AI om verbeteringen
---
### Mini-Project Ideeën
| Project | Core Feature | Complexiteit |
|---------|-------------|--------------|
| **Weer Widget** | 3-daagse forecast | ⭐ |
| **Quiz App** | 3 vragen + score | ⭐ |
| **Recipe Card** | Ingrediënten toggle | ⭐ |
| **Pomodoro Timer** | Start/stop + countdown | ⭐⭐ |
| **Expense Tracker** | Lijst + totaal | ⭐⭐ |
**Relations:**
- users → favorites (one-to-many)
- users → saved_recipes (one-to-many)
- recipes → favorites (one-to-many)
---
## Tools
- Cursor
- ChatGPT (voor planning)
- v0.dev (voor prototypes)
- OpenCode/WebStorm (voor implementation)
- Pen & papier (voor sketches)
---
## Lesopdracht (2 uur)
## Lesopdracht (2 uur, klassikaal)
### Bouw Je Mini-Prototype
### Kies je Eindproject & Plan Architectuur
**Groepsdiscussie (15 min):**
Bespreek klassikaal de Tool Selection reflecties uit Les 10 - welke workflows werken het beste?
Bespreek klassikaal de styling ervaringen uit Les 10 - welke Tailwind en shadcn/ui patterns werkten goed?
**Deel 1: Planning (30 min)**
**Deel 1: Idee Kiezen (30 min)**
1. Kies een project uit de lijst (of bedenk eigen simpel idee)
2. Schrijf je idee in 1-2 zinnen
3. Vraag ChatGPT om feature breakdown
4. Selecteer MVP features (max 3)
5. Schets de components op papier
Kies één van deze projecten OF je eigen idee (met goedkeuring docent):
- **AI Recipe Generator** - Input: ingredients, Output: recipe suggestions + cooking tips
- **Smart Budget Buddy** - Track expenses, AI insights on spending patterns
- **Travel Planner AI** - Generate itineraries based on preferences
- **Study Buddy AI** - Quiz generation, note-taking, Q&A helper
- **Jouw eigen idee**
**Deel 2: Bouwen (1 uur)**
**Deel 2: Feature Breakdown (30 min)**
1. Genereer UI in v0.dev
2. Open project in OpenCode
3. Integreer en pas aan
4. Zorg dat het werkt (happy path)
Voor je gekozen project:
1. Schrijf project description (2-3 zinnen)
2. Maak 3-5 user stories
3. Break down in core vs nice-to-have features
4. Selecteer MVP (wat is essentieel?)
**Focus op WORKFLOW, niet perfectie!**
**Deel 3: Architecture Planning (45 min)**
**Deel 3: Documentatie (15 min)**
1. Schets database schema
2. List de main components
3. Plan folder structure
4. Identify donde AI integreert (tool calling? Chat? Completion?)
Maak `docs/PROTOTYPE-LOG.md`:
- Je idee
- Feature breakdown
- MVP keuzes
- Prompts die werkten
- Wat ging fout en hoe opgelost
**Deel 4: Start Codebase (15 min)**
1. Maak GitHub repo
2. Create-next-app setup
3. Push initial commit
### Deliverable
- Werkend prototype (kan simpel zijn)
- `docs/PROTOTYPE-LOG.md` met je proces
- Screenshot van werkend prototype
- Project description document
- Feature breakdown met user stories
- Database schema diagram (hand-drawn ok)
- Component tree visualization
- GitHub repo met initial setup
---
## Huiswerk (2 uur)
### Verbeter en Reflecteer
### Bouw Basis Architecture
**Deel 1: Prototype Verbeteren (1 uur)**
**Deel 1: Folder Structure (30 min)**
1. Fix eventuele bugs
2. Voeg 1 extra feature toe
3. Verbeter styling
4. Handle 1 edge case
Create proper folder structure:
- src/components/ui/
- src/components/features/
- src/lib/
- src/hooks/
- src/types/
- docs/
**Deel 2: Peer Review (30 min)**
**Deel 2: Type Definitions (30 min)**
- Deel je prototype met een klasgenoot
- Krijg feedback
- Geef feedback op hun prototype
Schrijf TypeScript types voor je project:
- User type
- Main data types
- API response types
**Deel 3: Reflectie (30 min)**
**Deel 3: Component Skeleton (1 uur)**
Schrijf "Lessons Learned" document (300 woorden):
- Wat ging goed in je workflow?
- Waar liep je vast?
- Welke tool was het meest nuttig?
- Wat doe je volgende keer anders?
- Hoe voelde het om met AI te bouwen vs alleen?
Maak skeleton components:
- Main layout
- 3-4 main feature components (without logic)
- Proper props interfaces
- Basic Tailwind styling
### Deliverable
- Verbeterd prototype
- Peer review feedback (gegeven en ontvangen)
- Lessons Learned document (300 woorden)
- GitHub commits met folder structure
- types/ folder met je data types
- Skeleton components
- README.md met project description
---
## Leerdoelen
Na deze les kan de student:
- Van een vaag idee naar concrete features gaan
- AI gebruiken voor feature breakdown
- MVP denken toepassen (essentieel vs nice-to-have)
- Een app opdelen in components
- De complete workflow doorlopen (idee → prototype)
- Het bouwproces documenteren
- Reflecteren op wat werkt en wat niet
- Een project idee van vaag naar concreet uitwerken
- User stories schrijven
- Feature breakdown maken
- MVP bepalen
- Database schema ontwerpen
- Component architecture plannen
- Project codebase opzetten
- TypeScript types structureren
- Voorbereiding maken op verdere implementatie

View File

@@ -1,261 +1,157 @@
# Les 12: Introduction to Cursor
# Les 12: Project Setup & AI Config (.cursorrules, claude.md)
---
## Hoofdstuk
**Deel 3: AI Tooling & Prototyping** (Les 10-12)
**Deel 3: Full-Stack Development** (Les 9-12)
## Beschrijving
Kennismaking met Cursor - de professionele AI code editor. Leer de core features en ontdek waarom dit de tool is voor serieuze AI-assisted development.
Professional project setup en AI configuration voor optimal developer experience. Setup .cursorrules, claude.md, docs/ folder, folder structure optimalisatie en git best practices.
---
## Te Behandelen
## Te Behandelen (~45 min)
### Groepsdiscussie (15 min)
Bespreek klassikaal de prototype ervaringen uit Les 11 - welke workflow patterns werkten goed? Wat ging fout en hoe loste je dat op?
### Waarom Cursor?
**Tot nu toe gebruikten we:**
- OpenCode (gratis, goed voor leren)
- Claude Desktop (voor agents en projects)
**Cursor is de volgende stap:**
- Purpose-built voor AI-assisted coding
- Professionele editor (gebaseerd op VS Code)
- Superieure AI integratie
- Free tier beschikbaar (voldoende voor de cursus)
---
### Free vs Pro
| Aspect | Free Tier | Pro ($20/maand) |
|--------|-----------|-----------------|
| Tab completion | ✅ | ✅ |
| CMD+K edits | Beperkt | Onbeperkt |
| Chat | Beperkt | Onbeperkt |
| Composer | Beperkt | Onbeperkt |
| Models | GPT-5, Claude | Alle modellen |
**Voor deze cursus:** Free tier is voldoende!
---
### Installatie
1. Ga naar [cursor.sh](https://cursor.sh)
2. Download voor jouw OS
3. Installeer
4. Open Cursor
5. Sign in met GitHub
**Eerste keer:**
- Cursor vraagt om settings te importeren van VS Code (optioneel)
- Accept default keybindings
---
### Core Features
#### 1. Tab Completion
AI-powered autocomplete die hele blokken code voorspelt.
**Hoe het werkt:**
- Begin met typen
- Cursor suggereert code in grijs
- Druk Tab om te accepteren
- Druk Escape om te negeren
**Tip:** Schrijf een comment over wat je wilt, en Tab completion vult de code in.
```typescript
// Function that calculates the total price with tax
// [Tab completion vult de functie in]
```
#### 2. CMD+K (Inline Editing)
Selecteer code en vraag AI om het aan te passen.
**Hoe het werkt:**
1. Selecteer code (of zet cursor op een regel)
2. Druk CMD+K (Mac) of Ctrl+K (Windows)
3. Typ je instructie
4. Enter om te genereren
5. Accept of Reject de wijziging
**Voorbeelden:**
- "Add error handling"
- "Convert to TypeScript"
- "Make this responsive"
- "Add loading state"
#### 3. Chat (Sidebar)
Converseer met AI over je code.
**Hoe het werkt:**
1. CMD+Shift+L opent Chat
2. Stel je vraag
3. AI heeft context van je open file
**Voorbeelden:**
- "Explain what this function does"
- "How can I optimize this?"
- "What's wrong with this code?"
#### 4. @ Mentions
Refereer naar files, folders, of documentatie.
**Syntax:**
- `@filename.tsx` - specifieke file
- `@folder/` - hele folder
- `@Docs` - officiële docs zoeken
- `@Web` - web zoeken
**Voorbeeld:**
```
@components/Button.tsx - How can I add a loading prop to this?
```
#### 5. Composer Mode
Multi-file generatie in één keer.
**Hoe het werkt:**
1. CMD+I opent Composer
2. Beschrijf wat je wilt bouwen
3. AI genereert meerdere files tegelijk
4. Review en accept changes
**Wanneer gebruiken:**
- Nieuwe features met meerdere components
- Refactoring over meerdere files
- Boilerplate code genereren
---
### Workflow Vergelijking
| Taak | OpenCode | Cursor |
|------|----------|--------|
| Snelle fix | Chat | CMD+K |
| Nieuwe component | Chat | Tab completion + CMD+K |
| Multi-file feature | Meerdere chats | Composer |
| Code uitleg | Chat | Chat + @ mentions |
| Refactoring | Chat | CMD+K of Composer |
---
### Keyboard Shortcuts Cheat Sheet
| Actie | Mac | Windows |
|-------|-----|---------|
| Tab completion accept | Tab | Tab |
| Inline edit | CMD+K | Ctrl+K |
| Open Chat | CMD+Shift+L | Ctrl+Shift+L |
| Open Composer | CMD+I | Ctrl+I |
| Accept suggestion | CMD+Y | Ctrl+Y |
| Reject suggestion | CMD+N | Ctrl+N |
- Waarom goede project structuur cruciaal is voor AI tools
- De ideale folder structuur voor Next.js + AI collaboration
- .cursorrules file: syntax, best practices, examples
- claude.md: project documentation voor Claude
- docs/ folder organization: AI-DECISIONS.md, ARCHITECTURE.md, PROMPT-LOG.md
- .env.local vs .env.example configuratie
- .gitignore en .cursorignore voor AI tools
- .git best practices: meaningful commits, proper history
- README setup met project info
- TypeScript configuration optimization
---
## Tools
- Cursor
- Git
- GitHub
---
## Lesopdracht (2 uur)
## Lesopdracht (2 uur, klassikaal)
### Cursor Verkennen
### Setup Je Eindproject Proper
**Deel 1: Setup (20 min)**
**Groepsdiscussie (15 min):**
Bespreek klassikaal de architecture planning uit Les 11 - welke design patterns voelen goed voor jullie eindproject?
1. Download en installeer Cursor
2. Sign in met GitHub
3. Open je Todo app project
4. Verken de interface
**Deel 1: Folder Structure (30 min)**
**Deel 2: Tab Completion (30 min)**
Zorg dat je project er zo uitziet:
```
project/
├── src/
│ ├── app/
│ ├── components/
│ │ ├── ui/
│ │ ├── layout/
│ │ └── features/
│ ├── lib/
│ ├── hooks/
│ └── types/
├── docs/
├── public/
├── .cursorrules
├── .env.local
├── .env.example
├── .gitignore
├── README.md
└── claude.md
```
Maak nieuwe file `src/components/LoadingSpinner.tsx`:
1. Typ comment: `// Loading spinner component with size prop`
2. Laat Tab completion de rest doen
3. Itereer met meer comments
4. Noteer: hoe goed is de completion?
**Deel 2: .cursorrules Writing (30 min)**
**Deel 3: CMD+K Oefenen (30 min)**
Maak comprehensive `.cursorrules`:
1. Project naam + beschrijving
2. Tech stack (Next.js 14, TypeScript, Tailwind, Supabase)
3. File structure conventions
4. Code conventions (naming, styling, error handling)
5. TypeScript strict rules
6. DO's en DON'Ts specifiek voor je project
Open je TodoList component:
1. Selecteer de list rendering code
2. CMD+K → "Add loading state with skeleton"
3. Selecteer een button
4. CMD+K → "Add disabled state while loading"
5. Selecteer een function
6. CMD+K → "Add try-catch with error toast"
Example snippets:
- "Named exports only, no default exports"
- "All components are functional with TypeScript"
- "Use Tailwind classes, no inline styles"
Noteer wat werkt en wat niet.
**Deel 3: Documentation Files (30 min)**
**Deel 4: Chat + @ Mentions (20 min)**
Maak in docs/ folder:
- `PROJECT-BRIEF.md` - Project overview, features
- `ARCHITECTURE.md` - Component structure, data flow
- `AI-DECISIONS.md` - Start document met AI choices
- `PROMPT-LOG.md` - Template voor prompts die je gebruikt
1. Open Chat (CMD+Shift+L)
2. Vraag: "@TodoList.tsx What could I improve in this component?"
3. Vraag: "@lib/supabase.ts How do I add real-time subscriptions?"
4. Probeer @Docs voor Next.js documentatie
**Deel 4: Git Setup (20 min)**
**Deel 5: Composer Proberen (20 min)**
1. Open Composer (CMD+I)
2. Vraag: "Create a Settings page with dark mode toggle and notification preferences. Use our existing component style."
3. Review de gegenereerde files
4. Accept of reject
1. Review `.gitignore` (include `.env.local`)
2. Make initial commit: "Initial project setup"
3. Push naar GitHub
4. Verify: `.env.local` NOT in git history
### Deliverable
- Screenshot van werkende Tab completion
- 3 voorbeelden van CMD+K edits
- Notities: wat werkt wel/niet goed
- Complete folder structure
- Comprehensive .cursorrules file
- Documentation files in docs/
- GitHub repo met clean initial commit
- README.md with project info
---
## Huiswerk (2 uur)
### Bouw Feature met Cursor
### Optimize Configuration & Start Building
**Deel 1: Feature Bouwen (1.5 uur)**
**Deel 1: tsconfig.json Optimization (30 min)**
Voeg "Due Dates" toe aan je Todo app:
1. Elk todo kan een due date hebben
2. Toon due date in de lijst
3. Sorteer op due date
4. Markeer overdue items in rood
1. Enable strict mode in TypeScript
2. Configure path aliases for cleaner imports:
```json
"@/*": ["./src/*"]
```
3. Set proper include/exclude
**Gebruik ALLE Cursor features:**
- Tab completion voor nieuwe code
- CMD+K voor aanpassingen
- Chat voor vragen
- Composer voor multi-file changes
**Deel 2: Tailwind & Component Setup (45 min)**
**Deel 2: Reflectie (30 min)**
1. Customize `tailwind.config.ts` with your colors
2. Setup `globals.css` properly
3. Create 5 base UI components:
- Button.tsx
- Input.tsx
- Card.tsx
- Layout.tsx
- Navigation.tsx
Schrijf vergelijking (400 woorden):
- Cursor vs OpenCode: wat is beter?
- Welke feature gebruik je het meest?
- Is free tier voldoende?
- Ga je overstappen?
**Deel 3: Lib Setup (30 min)**
Create essential lib files:
- `lib/supabase.ts` - Initialize Supabase client
- `lib/utils.ts` - Utility functions
- `lib/constants.ts` - App constants
- `types/database.ts` - Database types
### Deliverable
- Werkende Due Dates feature
- GitHub commit met de changes
- Reflectie (400 woorden)
- Optimized TypeScript config with path aliases
- Base UI components created
- Lib utilities setup
- Documentation updated with decisions
- GitHub commits with setup progress
---
## Leerdoelen
Na deze les kan de student:
- Cursor installeren en configureren
- Tab completion effectief gebruiken
- CMD+K gebruiken voor inline edits
- Chat gebruiken met @ mentions voor context
- Composer mode gebruiken voor multi-file generatie
- Het verschil beoordelen tussen Cursor en OpenCode
- De juiste Cursor feature kiezen per taak
- Een professional project structure opzetten
- Een effectieve .cursorrules file schrijven
- claude.md projectdocumentatie maken
- AI-DECISIONS.md beginnen en onderhouden
- Git best practices volgen
- TypeScript strict mode configureren
- Path aliases voor cleaner imports opzetten
- Base components en utilities creëren
- Voorbereiding maken op Deel 4 (Advanced AI)

View File

@@ -1,345 +1,298 @@
# Les 13: Prompt Engineering & Custom GPTs
# Les 13: Vercel AI SDK, Tool Calling & Agents
---
## Hoofdstuk
**Deel 4: Advanced AI Features** (Les 13-18)
**Deel 4: Advanced AI & Deployment** (Les 13-18)
## Beschrijving
Verdiep je in advanced prompt engineering en leer eigen AI assistenten maken met Custom GPTs en Claude Projects. Focus op no-code manieren om AI te personaliseren voor jouw workflow.
Vercel AI SDK fundamentals voor het bouwen van AI-powered features. Stream responses, tool calling, Zod schemas, system prompts, agents met autonome actie. Integreer LLM capabilities in je app.
---
## Te Behandelen
## Te Behandelen (~45 min)
### Groepsdiscussie (15 min)
Bespreek klassikaal de Cursor reflecties uit Les 12 - welke features werken het beste voor welke taken?
- Vercel AI SDK: wat is het en waarom gebruiken?
- Installation en basic setup
- useChat hook voor chat UI state management
- Streaming responses van API
- Tool calling: laat AI externe APIs aanroepen
- Zod schemas voor tool parameters validation
- System prompts schrijven voor AI behavior
- Agent patterns: maxSteps, autonomous execution
- Error handling en edge cases
- Model selection: OpenAI, Claude, Gemini, etc.
### Advanced Prompt Engineering
---
**Recap van basis technieken (Les 4):**
- Zero-shot vs few-shot prompting
- Chain-of-thought reasoning
- Role prompting
### Vercel AI SDK Basics
**Nieuwe technieken:**
**Wat is het?**
- React library van Vercel voor AI integration
- Streaming responses van LLMs
- Server-side tool calling
- Multi-turn conversations
- Gratis, open-source
#### 1. Structured Output Prompting
```
Analyseer deze code en geef feedback in dit exacte format:
## Samenvatting
[1 zin over de code]
## Sterke punten
- [punt 1]
- [punt 2]
## Verbeterpunten
- [punt 1 met code voorbeeld]
- [punt 2 met code voorbeeld]
## Prioriteit
[Hoog/Medium/Laag]: [waarom]
```
#### 2. Constraint-Based Prompting
```
Schrijf een React component met deze constraints:
- Maximaal 50 regels code
- Geen externe dependencies
- TypeScript met strict types
- Alleen Tailwind voor styling
- Inclusief error handling
```
#### 3. Iterative Refinement
```
Stap 1: "Schrijf een basis login form"
Stap 2: "Voeg validatie toe"
Stap 3: "Voeg loading states toe"
Stap 4: "Voeg error handling toe"
Stap 5: "Optimaliseer voor accessibility"
**Installation:**
```bash
npm install ai zod openai
```
---
### Custom GPTs
### useChat Hook
**Wat zijn Custom GPTs?**
Gespecialiseerde ChatGPT versies met:
- Specifieke instructies (personality, expertise)
- Eigen kennis (uploaded files)
- Optioneel: Actions (API calls)
**Client-side chat state management:**
**Wanneer een Custom GPT maken:**
- Repetitieve taken met dezelfde context
- Specifieke expertise nodig
- Delen met team of anderen
```typescript
'use client'
import { useChat } from 'ai/react'
**Custom GPT maken:**
1. Ga naar chat.openai.com/gpts
2. Klik "Create"
3. Configureer in "Create" tab OF gebruik "Configure"
export function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: '/api/chat',
})
**Voorbeeld: Code Review GPT**
```
Instructions:
Je bent een code reviewer gespecialiseerd in React/Next.js.
Bij elke code review check je:
1. TypeScript best practices
2. React hooks correct gebruik
3. Performance (unnecessary re-renders)
4. Accessibility basics
5. Error handling
Geef feedback in dit format:
- ✅ Goed: [wat goed is]
- ⚠️ Suggestie: [verbeterpunten]
- ❌ Issue: [problemen die gefixed moeten worden]
```
**Voorbeeld: Project Assistant GPT**
```
Instructions:
Je bent mijn persoonlijke development assistant voor [project naam].
Tech stack:
- Next.js 14 met App Router
- TypeScript strict mode
- Tailwind CSS
- Supabase voor backend
Je kent de volgende conventies:
- Named exports (geen default exports)
- Nederlandse comments
- Error handling met try/catch
Wanneer ik code vraag:
1. Vraag eerst om context als die ontbreekt
2. Geef TypeScript code met types
3. Voeg korte uitleg toe
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
placeholder="Type message..."
/>
<button type="submit">Send</button>
</form>
</div>
)
}
```
---
### Claude Projects
### Streaming Responses
**Wat is een Claude Project?**
- Verzameling van context specifiek voor één doel
- Blijft behouden over conversaties
- Kan bestanden bevatten als knowledge base
**API Route met streaming:**
**Wanneer gebruiken:**
- Terugkerend werk aan hetzelfde project
- Consistente coding style nodig
- Documentatie die AI moet kennen
```typescript
import { generateText, streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
**Project aanmaken:**
1. Ga naar claude.ai → Projects
2. Klik "New Project"
3. Voeg project knowledge toe (files, instructies)
4. Start conversaties binnen het project
export async function POST(req: Request) {
const { messages } = await req.json()
**Voorbeeld Project Instructions:**
```
Je bent een expert React/Next.js developer die mij helpt met [project].
const result = await streamText({
model: openai('gpt-4'),
system: 'You are a helpful assistant',
messages,
})
Technologie stack:
- Next.js 14 met App Router
- TypeScript strict mode
- Tailwind CSS
- Supabase voor backend
- React Query voor data fetching
Coding conventions:
- Functional components met TypeScript
- Named exports (geen default exports)
- Error handling met try/catch
- Nederlandse comments in code
Wanneer je code schrijft:
- Gebruik altijd TypeScript types
- Voeg JSDoc comments toe voor complexe functies
- Denk aan edge cases en error handling
return result.toAIStreamResponse()
}
```
**Project Knowledge toevoegen:**
- Upload je belangrijkste files (schema, README, .cursorrules)
- Upload voorbeeldcode die je stijl toont
- Upload documentatie van libraries die je gebruikt
**Waarom streaming?**
- Responses verschijnen real-time (beter UX)
- Bespaar tokens vs waiting for full response
---
### Custom GPTs vs Claude Projects
### Tool Calling
| Aspect | Custom GPT | Claude Project |
|--------|------------|----------------|
| **Beschikbaar** | ChatGPT Plus ($20/maand) | Claude Pro ($20/maand) |
| **Knowledge** | File uploads (tot 20 files) | File uploads (tot 200k tokens) |
| **Delen** | Kan gepubliceerd worden | Alleen persoonlijk |
| **Actions** | Ja (API calls) | Nee |
| **Context window** | ~128k tokens | ~200k tokens |
| **Beste voor** | Gedeelde tools, API integratie | Persoonlijke projecten, grote codebases |
**Laat AI externe APIs aanroepen:**
```typescript
import { generateText } from 'ai'
import { openai } from '@ai-sdk/openai'
import { z } from 'zod'
const tools = {
getWeather: {
description: 'Get weather for a city',
parameters: z.object({
city: z.string(),
}),
execute: async ({ city }: { city: string }) => {
// Call external API
const response = await fetch(`https://api.weather.com?city=${city}`)
return response.json()
},
},
}
const result = await generateText({
model: openai('gpt-4'),
tools,
prompt: 'What is the weather in Amsterdam?',
})
```
---
### Prompt Templates Library
### Zod Schemas
**Code Generation:**
```
Context: [beschrijf je project/stack]
Taak: [wat moet er gemaakt worden]
Requirements:
- [requirement 1]
- [requirement 2]
Constraints:
- [constraint 1]
Output: [gewenst format]
**Type-safe tool parameters:**
```typescript
import { z } from 'zod'
const SearchProductsSchema = z.object({
query: z.string().describe('Search query'),
limit: z.number().optional().describe('Max results'),
sortBy: z.enum(['price', 'rating']).optional(),
})
type SearchProductsInput = z.infer<typeof SearchProductsSchema>
```
**Debugging:**
```
Error message:
[plak error]
---
Relevante code:
[plak code]
### System Prompts
Wat ik verwacht:
[gewenst gedrag]
**Stuur AI behavior:**
Wat er gebeurt:
[actueel gedrag]
```typescript
const systemPrompt = `You are a helpful recipe assistant.
Your role is to:
1. Suggest recipes based on ingredients
2. Provide cooking instructions
3. Estimate cooking time
Always be friendly and encouraging.`
const result = await generateText({
model: openai('gpt-4'),
system: systemPrompt,
prompt: userMessage,
})
```
**Code Review:**
```
Review de volgende code op:
1. Best practices voor [framework]
2. Potentiële bugs
3. Performance issues
4. Security vulnerabilities
---
[plak code]
### Agent Patterns
Geef feedback in format: ✅ Goed / ⚠️ Suggestie / ❌ Issue
**Multi-step autonomous execution:**
```typescript
const result = await generateText({
model: openai('gpt-4'),
tools: { getWeather, getFlights },
maxSteps: 3, // Maximum iterations
prompt: 'Plan a trip to Paris next week',
})
```
**Refactoring:**
```
Refactor deze code met de volgende doelen:
- [doel 1, bijv. "verbeter leesbaarheid"]
- [doel 2, bijv. "reduceer duplicatie"]
Behoud dezelfde functionaliteit.
Leg uit wat je verandert en waarom.
[plak code]
```
**Hoe het werkt:**
1. AI bepaalt welke tool nodig is
2. Tool wordt uitgevoerd
3. Result teruggestuurd naar AI
4. AI beslist next stap (repeat tot maxSteps of done)
---
## Tools
- ChatGPT (Custom GPTs)
- Claude (Projects)
- Prompt template documenten
- Vercel AI SDK
- Zod
- OpenAI API (of andere LLM provider)
- Cursor
---
## Lesopdracht (2 uur)
## Lesopdracht (2 uur, klassikaal)
### Bouw Je Eigen AI Assistants
### Bouw Chat Interface met Streaming
**Deel 1: Claude Project (45 min)**
**Groepsdiscussie (15 min):**
Bespreek klassikaal de project setup ervaringen uit Les 12 - hoe goed werken jullie .cursorrules en configuration?
Maak een Claude Project voor je eindproject:
1. Ga naar claude.ai → Projects → New Project
2. Schrijf project instructions:
- Tech stack
- Coding conventions
- Project specifieke regels
3. Upload relevante files (schema, README)
4. Test met 3 verschillende vragen
**Deel 1: Installation & Setup (30 min)**
**Deel 2: Custom GPT (45 min)**
```bash
npm install ai zod openai
```
Maak een Custom GPT voor code review:
1. Ga naar chat.openai.com/gpts
2. Klik "Create"
3. Schrijf instructions voor jouw stack
4. Test met code uit je project
Create `app/api/chat/route.ts`:
- Setup Vercel AI SDK
- Configure OpenAI model
- Add system prompt
**Deel 3: Vergelijking (30 min)**
**Deel 2: Chat Component (45 min)**
Test dezelfde taak met beide:
- "Review deze component op best practices"
- Noteer verschillen in output
- Welke is beter voor welk doel?
Build `app/page.tsx`:
1. Use useChat hook
2. Render messages list
3. Input form for user messages
4. Display streaming responses
**Deel 3: Tool Calling (30 min)**
Add 2 simple tools:
- getTime: return current time
- getRandomNumber: return random number
Update API route to handle tools with Zod schemas.
**Deel 4: Testing (15 min)**
Test chat locally with different prompts that trigger tools.
### Deliverable
- Claude Project URL of screenshot
- Custom GPT (als je ChatGPT Plus hebt) of instructies doc
- Vergelijkingsnotities
- Werkende chat interface with streaming
- 2 integrated tools
- GitHub commit with AI chat feature
---
## Huiswerk (2 uur)
### Optimaliseer Je AI Assistants
### Integreer AI in Eindproject
**Deel 1: Iteratie (1 uur)**
**Deel 1: Project-Specific Tools (1 uur)**
Verbeter je Claude Project:
1. Test met 5 verschillende taken
2. Noteer waar instructies tekortschieten
3. Pas instructies aan
4. Test opnieuw
Add 2-3 tools relevant to your project:
- Recipe Generator: tool to search recipes API
- Budget App: tool to calculate expenses
- Travel Planner: tool to search destinations
Documenteer in `docs/AI-ASSISTANTS.md`:
- Originele instructies
- Wat niet werkte
- Verbeterde instructies
- Resultaat verschil
Define with Zod schemas and execute functions.
**Deel 2: Prompt Library (30 min)**
**Deel 2: System Prompt Tuning (30 min)**
Maak een persoonlijke prompt library:
```markdown
# Mijn Prompt Templates
Write a custom system prompt for your AI:
- Define personality
- Set constraints
- Add context about your app
## Code Generation
[template]
**Deel 3: Integration (30 min)**
## Debugging
[template]
## Code Review
[template]
## Refactoring
[template]
```
**Deel 3: Reflectie (30 min)**
Schrijf reflectie (300 woorden):
- Welke AI assistant gebruik je waarvoor?
- Wat is het verschil tussen Claude Projects en Custom GPTs?
- Hoe heeft dit je workflow verbeterd?
Connect AI chat to your main app:
- Add chat page/component
- Integrate with Supabase auth (if needed)
- Test end-to-end
### Deliverable
- Geoptimaliseerde AI assistant instructies
- Prompt library document
- Reflectie (300 woorden)
- AI feature integrated in project
- Custom tools defined
- docs/AI-DECISIONS.md updated with choices
- GitHub commits with AI integration
---
## Leerdoelen
Na deze les kan de student:
- Advanced prompt engineering technieken toepassen (structured output, constraints, iterative refinement)
- Een Custom GPT maken met specifieke instructies en knowledge
- Een Claude Project opzetten met project context
- De juiste tool kiezen (Custom GPT vs Claude Project) per use case
- Een persoonlijke prompt library opbouwen en onderhouden
- Vercel AI SDK installeren en configureren
- useChat hook gebruiken voor chat UI
- Streaming responses implementeren
- Tool calling setup met Zod schemas
- Externe APIs aanroepen via tools
- System prompts schrijven voor AI behavior
- Agent patterns verstaan (maxSteps)
- AI features in een Next.js app integreren
- Tool parameters valideren met Zod

View File

@@ -1,444 +1,330 @@
# Les 14: Project Setup & Repository Structure
# Les 14: AI Chat Interface & Streaming
---
## Hoofdstuk
**Deel 4: Advanced AI Features** (Les 13-18)
**Deel 4: Advanced AI & Deployment** (Les 13-18)
## Beschrijving
Leer professionele project setup en repository structuur. Begrijp hoe een goed georganiseerd project AI tools effectiever maakt en samenwerking vergemakkelijkt.
Bouwen van professionele chat interfaces met streaming responses. Message rendering, markdown support, error handling, loading states, en UX patterns voor AI-powered features.
---
## Te Behandelen
## Te Behandelen (~45 min)
### Groepsdiscussie (15 min)
Bespreek klassikaal de AI assistant reflecties uit Les 13 - welke instructies werkten goed en welke niet?
### Waarom Project Structuur Belangrijk Is
**Voor jezelf:**
- Sneller code terugvinden
- Makkelijker onderhouden
- Minder bugs door consistentie
**Voor AI tools:**
- Betere context understanding
- Consistentere code generation
- Cursor/Claude begrijpt je project beter
**Voor samenwerking:**
- Anderen begrijpen je code sneller
- Standaard conventies = minder discussie
- Onboarding nieuwe developers eenvoudiger
- Chat UI patterns en best practices
- useChat hook deep dive (state, loading, error)
- Streaming response handling en real-time updates
- Message rendering strategies en optimizations
- Markdown rendering in chat messages
- Error handling en error boundaries
- Loading states en skeleton loaders
- User input validation and sanitization
- Accessibility in chat interfaces (ARIA labels)
- Message persistence (localStorage of database)
- Performance optimization
---
### Next.js 14 Project Structuur
### useChat Hook Deep Dive
**Aanbevolen structuur:**
```
project-root/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (auth)/ # Route group voor auth pagina's
│ │ │ ├── login/
│ │ └── register/
│ ├── api/ # API routes
│ │ └── chat/
│ │ ├── dashboard/
│ │ ├── layout.tsx
│ │ ├── page.tsx
└── globals.css
│ │
├── components/ # React components
│ ├── ui/ # Basis UI components
│ │ │ ├── Button.tsx
│ │ │ ├── Input.tsx
│ │ └── Card.tsx
│ ├── layout/ # Layout components
│ ├── Header.tsx
├── Footer.tsx
└── Sidebar.tsx
└── features/ # Feature-specifieke components
├── auth/
│ │ └── dashboard/
│ │
│ ├── lib/ # Utilities en configuraties
├── supabase.ts # Supabase client
├── utils.ts # Helper functies
│ └── constants.ts # App constanten
│ ├── hooks/ # Custom React hooks
├── useAuth.ts
└── useTodos.ts
│ │
│ └── types/ # TypeScript types
├── database.ts
│ └── api.ts
├── public/ # Static assets
│ ├── images/
│ └── favicon.ico
├── docs/ # Documentatie
│ ├── PROMPT-LOG.md
│ ├── AI-DECISIONS.md
│ └── PROJECT-BRIEF.md
├── .cursorrules # Cursor AI configuratie
├── .env.local # Environment variables (niet in git!)
├── .env.example # Template voor env vars
├── .gitignore
├── package.json
├── tsconfig.json
├── tailwind.config.ts
└── README.md
**State management met useChat:**
```typescript
'use client'
import { useChat } from 'ai/react'
export function ChatComponent() {
const {
messages, // All messages in conversation
input, // Current input text
handleInputChange, // Update input
handleSubmit, // Send message
isLoading, // Is AI responding?
error, // Any errors?
} = useChat({
api: '/api/chat',
initialMessages: [], // Optional: pre-load messages
})
return (
<>
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
</div>
{isLoading && <div>AI is thinking...</div>}
{error && <div>Error: {error.message}</div>}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>Send</button>
</form>
</>
)
}
```
---
### Component Organisatie
### Message Rendering Patterns
**UI Components (src/components/ui/):**
- Herbruikbare, generieke components
- Geen business logic
- Props-driven
- Voorbeelden: Button, Input, Modal, Card
**Layout Components (src/components/layout/):**
- Structurele components
- Meestal één per type
- Voorbeelden: Header, Footer, Sidebar, Navigation
**Feature Components (src/components/features/):**
- Business logic bevattend
- Specifiek voor één feature
- Groepeer per feature/domein
---
### File Naming Conventions
**Components:**
```
✅ Button.tsx # PascalCase
✅ UserProfile.tsx
❌ button.tsx
❌ user-profile.tsx
**Basic pattern:**
```typescript
<div className="space-y-4">
{messages.map((msg) => (
<div
key={msg.id}
className={msg.role === 'user' ? 'ml-auto' : 'mr-auto'}
>
<div className="bg-gray-200 p-3 rounded">
{msg.content}
</div>
</div>
))}
</div>
```
**Hooks:**
```
✅ useAuth.ts # camelCase met 'use' prefix
✅ useTodos.ts
❌ UseAuth.ts
❌ auth-hook.ts
**With markdown rendering:**
```typescript
import ReactMarkdown from 'react-markdown'
<div className="bg-gray-200 p-3 rounded">
<ReactMarkdown>{msg.content}</ReactMarkdown>
</div>
```
**Utilities:**
```
✅ formatDate.ts # camelCase
✅ utils.ts
✅ constants.ts
```
**Types:**
```
✅ database.ts # camelCase
✅ User.types.ts # optioneel: .types suffix
**With message types:**
```typescript
{messages.map((msg) => (
<div key={msg.id} className={msg.role === 'user' ? 'user-message' : 'ai-message'}>
{msg.content}
</div>
))}
```
---
### .cursorrules Setup
### Error Handling
**Maak .cursorrules in project root:**
```markdown
# Project: [Jouw Project Naam]
**Structured error handling:**
## Tech Stack
- Next.js 14 met App Router
- TypeScript (strict mode)
- Tailwind CSS
- Supabase
- React Query
```typescript
try {
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ messages }),
})
## File Structure
- Components in src/components/
- UI components in src/components/ui/
- API routes in src/app/api/
if (!response.ok) {
throw new Error(`API error: ${response.status}`)
}
## Code Conventions
- Functional components only
- Named exports (geen default exports)
- Props interface boven component
- Nederlandse comments
// Handle streaming...
} catch (error) {
console.error('Chat error:', error)
setError({
message: 'Failed to send message',
code: error instanceof Error ? error.message : 'unknown'
})
}
```
## Naming
- Components: PascalCase (Button.tsx)
- Hooks: camelCase met use prefix (useAuth.ts)
- Utils: camelCase (formatDate.ts)
## Styling
- Tailwind CSS classes
- Geen inline styles
- Responsive mobile-first
## TypeScript
- Strict mode
- Geen any types
- Interfaces voor props
- Types voor data
## Don'ts
- Geen console.log in productie
- Geen hardcoded strings
- Geen unused imports
**Error boundary:**
```typescript
<ErrorBoundary fallback={<div>Chat error occurred</div>}>
<ChatComponent />
</ErrorBoundary>
```
---
### Git Best Practices
### Loading States
**Commit Message Format:**
```
type: korte beschrijving
**Skeleton loader:**
```typescript
function MessageSkeleton() {
return (
<div className="animate-pulse">
<div className="bg-gray-300 h-4 rounded w-48 mb-2" />
<div className="bg-gray-300 h-4 rounded w-64" />
</div>
)
}
Types:
- feat: nieuwe feature
- fix: bug fix
- refactor: code verbetering
- docs: documentatie
- style: formatting
- test: tests toevoegen
```
**Voorbeelden:**
```bash
git commit -m "feat: add user authentication with Supabase"
git commit -m "fix: resolve hydration error in TodoList"
git commit -m "docs: update README with setup instructions"
```
**.gitignore essentials:**
```
# Dependencies
node_modules/
# Environment
.env*.local
# Next.js
.next/
out/
# IDE
.idea/
.vscode/
# OS
.DS_Store
{isLoading && <MessageSkeleton />}
```
---
### Environment Variables
### Input Validation
**Structuur:**
```bash
# .env.local (NOOIT committen!)
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
OPENAI_API_KEY=sk-...
**Validate before sending:**
# .env.example (WEL committen)
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
OPENAI_API_KEY=your-openai-key
```typescript
function handleSubmit(e: React.FormEvent) {
e.preventDefault()
// Trim whitespace
const trimmedInput = input.trim()
// Validate non-empty
if (!trimmedInput) {
setError('Message cannot be empty')
return
}
// Validate length
if (trimmedInput.length > 1000) {
setError('Message too long (max 1000 chars)')
return
}
// Send message
handleSubmit(e)
}
```
**Regels:**
- `NEXT_PUBLIC_` prefix = zichtbaar in browser
- Zonder prefix = alleen server-side
- Nooit secrets in `NEXT_PUBLIC_` vars
---
### README.md Template
### Message Persistence
```markdown
# Project Naam
**Save to localStorage:**
Korte beschrijving van je project.
```typescript
const [messages, setMessages] = useState(() => {
const saved = localStorage.getItem('chat_history')
return saved ? JSON.parse(saved) : []
})
## Features
- Feature 1
- Feature 2
- Feature 3
// Save whenever messages change
useEffect(() => {
localStorage.setItem('chat_history', JSON.stringify(messages))
}, [messages])
```
## Tech Stack
- Next.js 14
- TypeScript
- Tailwind CSS
- Supabase
- Vercel AI SDK
## Getting Started
### Prerequisites
- Node.js 18+
- npm of yarn
- Supabase account
### Installation
1. Clone de repository
```bash
git clone https://github.com/username/project.git
cd project
```
2. Installeer dependencies
```bash
npm install
```
3. Maak .env.local (zie .env.example)
4. Start development server
```bash
npm run dev
```
## Environment Variables
Zie `.env.example` voor benodigde variabelen.
## Deployment
Deployed op Vercel: [productie-url]
## Documentatie
- [PROMPT-LOG.md](docs/PROMPT-LOG.md)
- [AI-DECISIONS.md](docs/AI-DECISIONS.md)
**Save to database:**
```typescript
const saveMessage = async (message: Message) => {
await fetch('/api/messages', {
method: 'POST',
body: JSON.stringify({
content: message.content,
role: message.role,
userId: user.id,
}),
})
}
```
---
## Tools
- Vercel AI SDK
- React Markdown
- Cursor
- Git
- GitHub
- TypeScript
---
## Lesopdracht (2 uur)
## Lesopdracht (2 uur, klassikaal)
### Setup Je Eindproject
### Build Professional Chat Interface
**Deel 1: Project Structuur (45 min)**
**Groepsdiscussie (15 min):**
Bespreek klassikaal de Vercel AI SDK ervaringen uit Les 13 - welke tool calling patterns werkten goed?
1. Maak nieuw Next.js project:
```bash
npx create-next-app@latest mijn-eindproject --typescript --tailwind --app
```
**Deel 1: Chat Component (45 min)**
2. Maak de mappenstructuur:
- src/components/ui/
- src/components/layout/
- src/components/features/
- src/lib/
- src/hooks/
- src/types/
- docs/
Build components/ChatInterface.tsx:
1. Use useChat hook
2. Render messages with proper styling
3. User vs AI message styling
4. Input form with validation
5. Tailwind + shadcn/ui components
3. Maak placeholder files:
- src/lib/supabase.ts
- src/lib/utils.ts
**Deel 2: Markdown & Error Handling (30 min)**
**Deel 2: Configuratie (30 min)**
1. Install react-markdown: `npm install react-markdown`
2. Render AI responses with markdown
3. Add error boundary
4. Show error messages to user
5. Proper loading states
1. Maak .cursorrules met jouw conventies
2. Maak .env.example
3. Update .gitignore
4. Maak README.md met template
**Deel 3: UX Improvements (30 min)**
**Deel 3: Git Setup (25 min)**
1. Auto-scroll to latest message
2. Disable input while loading
3. Show message count/token usage
4. Add clear chat history button
5. Save messages to localStorage
1. git init
2. Initial commit met goede message
3. Push naar GitHub
4. Check: .env.local NIET gecommit?
**Deel 4: Testing (15 min)**
**Deel 4: Documentatie Start (20 min)**
Maak in docs/:
- PROJECT-BRIEF.md (beschrijving eindproject)
- PROMPT-LOG.md (leeg template)
- AI-DECISIONS.md (leeg template)
Test chat interface locally with various inputs and error scenarios.
### Deliverable
- GitHub repository met correcte structuur
- .cursorrules file
- README.md
- docs/ folder met templates
- Werkende chat interface component
- Markdown rendering working
- Error handling implemented
- LocalStorage persistence
- GitHub commit with chat UI
---
## Huiswerk (2 uur)
### Bouw Project Foundation
### Integrate Chat into Your Project
**Deel 1: Base Components (1 uur)**
**Deel 1: Project Integration (1 uur)**
Maak basis UI components met AI hulp:
- src/components/ui/Button.tsx
- src/components/ui/Input.tsx
- src/components/ui/Card.tsx
- src/components/layout/Header.tsx
- src/components/layout/Footer.tsx
1. Add chat component to your app
2. Connect to your API route with tools
3. Style to match your design
4. Test with actual tools/integrations
5. Fix any bugs
Requirements:
- TypeScript interfaces voor props
- Tailwind styling
- Responsive design
- Volg je .cursorrules
**Deel 2: Enhanced Features (30 min)**
**Deel 2: Supabase Setup (30 min)**
Add one of these:
- Message copy button
- Regenerate response option
- Clear history confirmation
- Export chat history
- Message timestamps
1. Maak Supabase project (of hergebruik van Les 9)
2. Configureer src/lib/supabase.ts
3. Voeg env vars toe aan .env.local
4. Test connectie
**Deel 3: Performance & Polish (30 min)**
**Deel 3: Eerste Feature (30 min)**
Kies je eindproject en implementeer 1 basisfeature:
- Recipe Generator: ingredient input form
- Budget Buddy: expense entry form
- Travel Planner: destination search
Commit en push!
1. Optimize re-renders (useMemo, useCallback)
2. Virtual scrolling for long chats
3. Better accessibility (keyboard nav)
4. Mobile responsive tweaks
5. Update docs/AI-DECISIONS.md
### Deliverable
- Werkende UI components
- Supabase connectie
- 1 basic feature
- Alle commits met goede messages
---
## 💡 Eindopdracht
Dit is een goed moment om te starten met **deelopdracht 1** van je eindopdracht. De setup die je vandaag maakt kun je direct gebruiken voor je eindproject. Bespreek je projectidee met de docent als je feedback wilt.
- Chat fully integrated in project
- Enhanced features implemented
- Performance optimized
- GitHub commits with improvements
---
## Leerdoelen
Na deze les kan de student:
- Een professionele project structuur opzetten
- File naming conventions toepassen
- Een effectieve .cursorrules file schrijven
- Git best practices volgen
- Environment variables correct beheren
- Een README.md schrijven
- Project documentatie structureren
- useChat hook volledig begrijpen en gebruiken
- Professionele chat UI patterns implementeren
- Markdown rendering in chat messages
- Error handling en error boundaries toepassen
- Loading states en skeletons bouwen
- User input valideren en sanitizen
- Message persistence (localStorage/DB)
- Accessibility in chat interfaces verbeteren
- Performance optimizations toepassen
- Complete chat feature in Next.js app integreren

View File

@@ -1,336 +1,130 @@
# Les 15: MCP & Context Management
# Les 15: Eindproject Werkdag 1
---
## Hoofdstuk
**Deel 4: Advanced AI Features** (Les 13-18)
**Deel 4: Advanced AI & Deployment** (Les 13-18)
## Beschrijving
Leer werken met Model Context Protocol (MCP) en geavanceerd context management. Begrijp hoe je AI tools maximale context geeft voor betere resultaten.
Eerste intensieve werkdag aan je eindproject. Focus op core features bouwen, Q&A sessions, individuele hulp, en probleemoplossing. Docent beschikbaar voor guidance en klassikale sparring.
---
## Te Behandelen
## Te Behandelen (~45 min guidance + 120 min work)
### Groepsdiscussie (15 min)
Bespreek klassikaal de project setup ervaringen uit Les 14 - welke structuur conventies werken goed en welke niet?
### Wat is Context?
**Context = wat de AI weet over jouw situatie**
**Soorten context:**
- **Code context:** welke files, welke functies
- **Project context:** tech stack, conventies
- **Taak context:** wat je probeert te bereiken
- **Historische context:** eerdere conversatie
**Meer context = betere antwoorden**
- Q&A session: wat loop je tegen aan?
- Cursor tips & tricks review
- Debugging strategies en common patterns
- Performance optimization basics
- Deployment troubleshooting preview
- Hands-on assistance beschikbaar
- Code review feedback
- Feature planning discussion
---
### Model Context Protocol (MCP)
## Doel van deze Werkdag
**Wat is MCP?**
- Open protocol van Anthropic
- Standaard manier om AI models context te geven
- Verbindt AI met externe tools en data
**Waarom MCP?**
- AI kan nu "tools gebruiken"
- Toegang tot je filesystem
- Database queries uitvoeren
- Web searches doen
- En meer...
**Hoe werkt het:**
```
Jij → vraag → AI → MCP → Tool → resultaat → AI → antwoord → Jij
```
- Core features van je eindproject afmaken
- Architecture validatie (is je setup goed?)
- Database integratie testen
- Auth flows uitwerken
- First AI feature implementeren
- Testing en bug fixes
---
### MCP in de Praktijk
### Checklist voor deze Sessie
**Cursor gebruikt MCP onder de hood:**
- `@file` mentions = file context via MCP
- `@codebase` = codebase search via MCP
- `@Docs` = documentation lookup via MCP
- `@Web` = web search via MCP
**Code Quality:**
- [ ] TypeScript geen errors
- [ ] No console.logs in production code
- [ ] Proper error handling
- [ ] Component props typed
**Claude Desktop met MCP:**
- Kan tools uitvoeren
- Filesystem access
- Terminal commands
- Database queries
**Features:**
- [ ] Core feature 1 werkend
- [ ] Core feature 2 werkend
- [ ] Data persistence (Supabase)
- [ ] Loading states visible
- [ ] Error states handled
---
### Context Management Strategieën
**1. Expliciete Context**
Geef context direct in je prompt:
```
Context: Next.js 14 project met TypeScript en Supabase.
Dit is een e-commerce app voor verkoop van boeken.
Vraag: Hoe implementeer ik een winkelwagen?
```
**2. File Context**
Gebruik @ mentions om relevante files toe te voegen:
```
@src/types/product.ts
@src/lib/supabase.ts
Hoe maak ik een addToCart functie?
```
**3. Project Context**
Gebruik .cursorrules of Claude Project instructions:
```
# In .cursorrules
Dit project is een e-commerce platform.
Alle prices zijn in cents (niet euros).
```
**4. Fresh Context**
Begin nieuwe chat voor nieuw onderwerp:
- Voorkomt context vervuiling
- AI raakt niet in de war met oude info
---
### @ Mentions Strategisch Gebruiken
**Cursor @ mentions:**
| Mention | Wanneer | Voorbeeld |
|---------|---------|-----------|
| `@file.tsx` | Specifieke file context | `@Button.tsx hoe voeg ik loading toe?` |
| `@folder/` | Hele folder context | `@components/ welke patterns gebruik ik?` |
| `@codebase` | Zoeken in project | `@codebase waar handle ik auth?` |
| `@Docs` | Officiële documentatie | `@Docs Next.js App Router` |
| `@Web` | Live web search | `@Web Supabase RLS policies` |
**Best practice:** Combineer mentions voor rijke context:
```
@src/components/auth/LoginForm.tsx
@src/lib/supabase.ts
@Docs Supabase Auth
Hoe voeg ik Google OAuth toe aan mijn login?
```
---
### Claude Projects voor Persistente Context
**Wat kun je toevoegen aan een Project:**
- Instructies (system prompt)
- Files (code, docs, schema's)
- Kennis die over sessies heen blijft
**Project Instructions Template:**
```markdown
# Project: [Naam]
## Tech Stack
- Next.js 14 met App Router
- TypeScript strict
- Tailwind CSS
- Supabase
## Code Conventies
[jouw conventies]
## Database Schema
[beschrijf je schema]
## Current Focus
[waar werk je nu aan]
```
**Tip:** Upload je database schema, README, en belangrijke types files.
---
### Context Windows en Limieten
**Wat is een context window?**
- Maximum hoeveelheid tekst die AI kan "zien"
- Gemeten in tokens (±4 karakters per token)
**Limieten per model:**
| Model | Context Window |
|-------|---------------|
| GPT-5 | 128K tokens |
| Claude 3 Sonnet | 200K tokens |
| Claude 3 Opus | 200K tokens |
**Praktisch:**
- Lange conversaties → context vol
- Veel file mentions → context vol
- Start fresh chat wanneer nodig
**Signs van vol context:**
- AI vergeet eerdere instructies
- Antwoorden worden minder relevant
- AI herhaalt zichzelf
---
### Context Hygiene
**Do's:**
- Geef alleen relevante context
- Wees specifiek in file mentions
- Start nieuwe chat voor nieuw onderwerp
- Gebruik project-level context voor consistentie
**Don'ts:**
- Hele codebase als context geven
- Oude irrelevante chats voortzetten
- Te veel files tegelijk mentionen
- Context herhalen die AI al heeft
---
### Debugging met Context
**Wanneer AI verkeerde antwoorden geeft:**
1. **Check context:** Heeft AI de juiste files?
2. **Check instructies:** Zijn project rules geladen?
3. **Fresh start:** Begin nieuwe chat
4. **Explicieter:** Voeg meer context toe in prompt
**Debug prompt:**
```
Ik merk dat je antwoord niet klopt.
Heb je toegang tot @src/lib/auth.ts?
Dit is de huidige implementatie: [plak code]
Kun je opnieuw kijken?
```
**Setup:**
- [ ] Project structure clean
- [ ] .cursorrules up-to-date
- [ ] Environment variables configured
- [ ] Git history clean
---
## Tools
- Cursor (@ mentions)
- Claude Projects
- ChatGPT
- Cursor
- Chrome DevTools
- Supabase Dashboard
- GitHub
- Vercel (optional preview)
---
## Lesopdracht (2 uur)
## Lesopdracht (4 uur, hands-on werk)
### Context Management Oefenen
### Intensive Development Session
**Deel 1: @ Mentions Mastery (45 min)**
**Hou je voortgang bij:**
Voer deze taken uit in Cursor:
**Eerste uur (0-60 min):**
- [ ] Maak prioriteiten list van features
- [ ] Fix top 3 bugs van vorige sessies
- [ ] Test alles lokaal
- [ ] Ask docent for guidance if stuck
1. **Single file context:**
- Open je TodoList component
- Vraag: "@TodoList.tsx Hoe kan ik infinite scroll toevoegen?"
- Noteer kwaliteit antwoord
**Twee uur (60-120 min):**
- [ ] Implementeer core feature 1 (CRUD)
- [ ] Implementeer core feature 2
- [ ] Add loading/error states
- [ ] Styling pass 1
2. **Multi-file context:**
- Vraag: "@src/components/ @src/types/ Welke types mis ik?"
- Noteer hoe context helpt
**Drie uur (120-180 min):**
- [ ] Implementeer AI feature (tool calling)
- [ ] Test end-to-end flows
- [ ] Fix bugs found
- [ ] Performance check
3. **Docs context:**
- Vraag: "@Docs Supabase realtime Hoe voeg ik real-time updates toe aan mijn todo app?"
- Noteer of antwoord up-to-date is
4. **Codebase search:**
- Vraag: "@codebase Waar handle ik error states?"
- Noteer of het de juiste plekken vindt
**Deel 2: Claude Project Setup (30 min)**
1. Maak Claude Project voor je eindproject
2. Schrijf comprehensive instructions
3. Upload 3-5 belangrijke files:
- Database schema/types
- Main component
- Supabase client
4. Test met 3 vragen
**Deel 3: Context Vergelijking (45 min)**
Voer dezelfde taak uit met:
1. Geen context (nieuwe chat, geen mentions)
2. File context (@mentions)
3. Project context (Claude Project)
Taak: "Implementeer een search feature voor todos"
Noteer:
- Kwaliteit code
- Relevantie voor jouw project
- Hoeveel aanpassing nodig
**Vier uur (180-240 min):**
- [ ] Polish UI
- [ ] Code cleanup
- [ ] Document decisions
- [ ] Commit and push
### Deliverable
- Notities over @ mentions effectiviteit
- Claude Project met instructions
- Context vergelijkingsnotities
- Werkende core features
- Updated PROMPT-LOG.md (add entries)
- Updated AI-DECISIONS.md
- GitHub commits with progress
- Screenshot of working features
---
## Huiswerk (2 uur)
## Huiswerk
### Optimaliseer Je Context Strategie
Continue building your project for next session.
**Deel 1: .cursorrules Perfectioneren (30 min)**
Update je .cursorrules met:
- Specifieke file structure info
- Naming conventions met voorbeelden
- Common patterns in je project
- DON'Ts specifiek voor jouw situatie
**Deel 2: Claude Project Uitbreiden (45 min)**
1. Upload alle relevante project files
2. Test met complexe vragen:
- "Hoe implementeer ik feature X?"
- "Review mijn auth implementatie"
- "Wat ontbreekt er nog?"
3. Itereer op instructions
**Deel 3: Context Documentation (45 min)**
Maak `docs/CONTEXT-GUIDE.md`:
```markdown
# Context Guide voor [Project]
## Cursor @ Mentions
- Voor UI changes: @src/components/
- Voor data logic: @src/lib/ @src/hooks/
- Voor types: @src/types/
## Claude Project
- Project URL: [link]
- Uploaded files: [lijst]
- Best practices: [jouw learnings]
## Common Prompts
[verzameling werkende prompts]
```
### Deliverable
- Geoptimaliseerde .cursorrules
- Complete Claude Project
- docs/CONTEXT-GUIDE.md
**Checklist:**
- [ ] Core features complete
- [ ] All changes pushed to GitHub
- [ ] PROMPT-LOG.md updated with prompts used
- [ ] AI-DECISIONS.md updated with choices made
- [ ] Ready for next workday session
---
## Leerdoelen
Na deze les kan de student:
- Uitleggen wat context is en waarom het belangrijk is
- Model Context Protocol (MCP) begrijpen
- @ mentions strategisch gebruiken in Cursor
- Een Claude Project opzetten met effectieve instructions
- Context windows en limieten begrijpen
- Context management best practices toepassen
- Debuggen wanneer AI verkeerde antwoorden geeft door context issues
- Zelfstandig problemen identificeren en fixen
- Cursor effectief gebruiken voor rapid development
- Architectuur decisions valideren en aanpassen
- Debugging strategieën toepassen
- Features end-to-end implementeren
- Development workflows versnellen
- Documentatie up-to-date houden

View File

@@ -1,346 +1,122 @@
# Les 16: Mastering Cursor
# Les 16: Eindproject Werkdag 2
---
## Hoofdstuk
**Deel 4: Advanced AI Features** (Les 13-18)
**Deel 4: Advanced AI & Deployment** (Les 13-18)
## Beschrijving
Verdieping in Cursor's geavanceerde features. Leer model keuze, Composer Mode, @ mentions, en .cursorrules optimaal gebruiken.
Verlengde werksessie voor je eindproject. Continue Development Day waar je intensief aan je AI-powered applicatie werkt met begeleiding en feedback van de docent.
---
## Te Behandelen
### Groepsdiscussie (15 min)
Bespreek klassikaal de context management ervaringen uit Les 15 - welke strategieën werkten het beste?
Bespreek klassikaal de deployment ervaringen uit Les 15 - welke problemen kwamen jullie tegen en hoe losten jullie die op?
### Model Keuze
### Doel van deze werkdag
**Wanneer welk model?**
| Model | Gebruik voor | Kosten |
|-------|-------------|--------|
| **Haiku** | Simpele taken, autocomplete | Goedkoop |
| **Sonnet** | Dagelijks werk, de meeste taken | Medium |
| **Opus** | Complexe architectuur, multi-file | Duur |
**Vuistregels:**
- Tab completion: Haiku (automatisch)
- CMD+K: Sonnet (default)
- Composer: Sonnet of Opus
- Complexe debugging: Opus
Deze les is puur praktisch werken:
- Bugs fixen die je bent tegengekomen
- Features afmaken die nog niet klaar zijn
- Code cleanup en optimalisatie
- Performance verbeteren
- Documentatie aanpassen
- Testen in productie
---
### Composer Mode Diepgaand
### Checklist voor deze sessie
**Wat is Composer?**
Multi-file generatie in één keer. Cursor plant en voert wijzigingen uit over meerdere bestanden.
**Code Quality:**
- [ ] TypeScript geen errors
- [ ] ESLint check passed
- [ ] Geen unused imports/variables
- [ ] Proper error handling overal
**Wanneer Composer gebruiken:**
- Nieuwe feature met meerdere components
- Refactoring over meerdere files
- Boilerplate generatie
- Complexe wijzigingen
**Features:**
- [ ] Alle core features werken
- [ ] AI feature functionaliteit compleet
- [ ] Edge cases afgehandeld
- [ ] Loading states geimplementeerd
- [ ] Error states geimplementeerd
**Composer Workflow:**
1. CMD+I opent Composer
2. Beschrijf je doel duidelijk
3. Voeg context toe met @ mentions
4. Laat Cursor plannen
5. Review het plan
6. Accept of reject per file
7. Itereer met feedback
**Performance:**
- [ ] Lighthouse score > 80
- [ ] Images optimized
- [ ] Lazy loading waar nodig
- [ ] Bundle size reasonable
**Voorbeeld prompt:**
```
Create a user profile page with:
- @components/ui/ style components
- Profile header with avatar
- Edit form with validation
- Save to @lib/supabase.ts
- Loading and error states
```
---
### @ Mentions Systeem
**Alle types:**
| Mention | Wat het doet | Voorbeeld |
|---------|--------------|-----------|
| `@file.tsx` | Specifieke file | `@Button.tsx` |
| `@folder/` | Hele folder | `@components/` |
| `@codebase` | Zoek in codebase | `@codebase auth logic` |
| `@Docs` | Officiële docs | `@Docs Next.js routing` |
| `@Web` | Web zoeken | `@Web Supabase auth setup` |
**Best practices:**
- Wees specifiek met file mentions
- Gebruik folder mentions voor context
- @Docs voor up-to-date informatie
- Combineer mentions voor betere context
---
### .cursorrules Advanced
**Meerdere rules files:**
```
.cursor/
└── rules/
├── general.mdc # Project-brede regels
├── components.mdc # Component conventies
├── api.mdc # API route regels
└── testing.mdc # Test conventies
```
**Effectieve rules schrijven:**
```markdown
# Component Rules
## Structure
Alle components moeten volgen:
1. Props interface bovenaan
2. Component function
3. Named export onderaan
## Example
\`\`\`tsx
interface ButtonProps {
label: string
onClick: () => void
variant?: 'primary' | 'secondary'
}
export function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
return (
<button onClick={onClick} className={...}>
{label}
</button>
)
}
\`\`\`
## DON'Ts
- Geen default exports
- Geen inline styles
- Geen any types
```
---
### Codebase Indexing
**Hoe Cursor indexeert:**
- Scant alle files in je project
- Bouwt semantic understanding
- Gebruikt voor autocomplete en context
**Optimaliseren:**
1. Goede `.cursorignore` (node_modules, .next, etc.)
2. Semantische naming
3. Duidelijke file structuur
4. Comments waar nodig
**Re-indexeren:**
CMD+Shift+P → "Reindex"
---
### Cost Management
**Token gebruik monitoren:**
- Cursor toont token count in chat
- Check monthly usage in settings
**Bespaartips:**
1. Gebruik Haiku voor simpele taken
2. Beperk context (niet hele codebase)
3. Wees specifiek in prompts
4. Fresh chat voor nieuwe onderwerpen
**Free tier strategie:**
- Focus op Tab completion (onbeperkt)
- Gebruik CMD+K spaarzaam
- Composer alleen voor grote taken
---
### Debugging met Cursor
**AI-Assisted Debugging:**
**Stap 1: Error identificeren**
```
@file-met-error.tsx
Ik krijg deze error: [plak error]
Wat gaat er mis?
```
**Stap 2: Context toevoegen**
```
@file-met-error.tsx
@gerelateerde-file.ts
De error treedt op wanneer ik X doe.
Console log toont: [data]
```
**Stap 3: Fix implementeren**
- Selecteer code met error
- CMD+K → "Fix this error: [beschrijving]"
- Review en test
---
### Refactoring met Cursor
**Pattern 1: Extract Component**
```
Selecteer JSX block → CMD+K
"Extract this into a separate component called ProductCard"
```
**Pattern 2: Extract Hook**
```
Selecteer state + useEffect → CMD+K
"Extract this into a custom hook called useProductData"
```
**Pattern 3: Improve Performance**
```
@Component.tsx
"Optimize this component:
- Add memoization waar nodig
- Fix unnecessary re-renders
- Improve loading performance"
```
**Deployment:**
- [ ] Builds succesvol lokaal
- [ ] Alle env vars in Vercel
- [ ] Works in production
- [ ] Supabase redirects configured
---
## Tools
- Cursor
- Claude models (Haiku/Sonnet/Opus)
- .cursorrules
- Chrome DevTools
- Vercel Dashboard
- Supabase Dashboard
- GitHub
---
## Lesopdracht (2 uur)
## Lesopdracht (4 uur)
### Multi-Step Form Wizard
### Intensive Development Session
**Bouw met Composer:**
**Hou je voortgang bij:**
| Stap | Features |
|------|----------|
| 1 | Personal info (naam, email) |
| 2 | Preferences (theme, notifications) |
| 3 | Review & confirm |
| 4 | Success animation |
**Eerste uur:**
- [ ] Maak prioriteiten list
- [ ] Fix top 3 bugs
- [ ] Test alles lokaal
**Requirements:**
- Progress indicator
- Per-stap validatie
- localStorage persistence
- TypeScript strict
- Tailwind styling
- Mobile responsive
**Twee uur:**
- [ ] Implementeer ontbrekende features
- [ ] Add missing error handling
- [ ] Improve styling/UX
**Process:**
**Drie uur:**
- [ ] Code cleanup
- [ ] Performance optimization
- [ ] Redeploy
**Deel 1: Composer Setup (30 min)**
1. Open Composer (CMD+I)
2. Schrijf comprehensive prompt
3. Include @ mentions naar relevante files
4. Kies Sonnet of Opus
**Deel 2: Generatie & Review (45 min)**
1. Laat Composer genereren
2. Review elke file
3. Accept wat goed is
4. Reject wat niet past
**Deel 3: Refinement (45 min)**
1. Gebruik CMD+K voor kleine fixes
2. Chat voor vragen
3. Itereer tot het werkt
**Vier uur:**
- [ ] Final testing in production
- [ ] Screenshot taken
- [ ] Docs updated
### Deliverable
- Werkende form wizard
- Notities: welk model wanneer, hoeveel iteraties
- Werkende app (lokaal en productie)
- Updated PROMPT-LOG.md
- Updated AI-DECISIONS.md
- Screenshot van werkende app
---
## Huiswerk (2 uur)
## Huiswerk
### Perfecte .cursorrules
**Deel 1: Research (30 min)**
- Zoek 3-5 .cursorrules voorbeelden online
- Analyseer wat ze effectief maakt
**Deel 2: Write Comprehensive Rules (1 uur)**
Maak complete .cursorrules voor je eindproject:
```markdown
# [Project Naam]
## Tech Stack
[Jouw stack]
## Code Conventions
[Jouw conventies]
## File Naming
[Jouw regels]
## Component Structure
[Jouw patterns]
## Styling
[Tailwind regels]
## API Routes
[Route conventies]
## Error Handling
[Error patterns]
## DON'Ts
[Wat te vermijden]
```
**Deel 3: Test (30 min)**
1. Start nieuw component
2. Vraag Cursor om het te bouwen
3. Check: volgt Cursor je regels?
4. Itereer indien nodig
Continue working on your end project for next session. Make sure to:
- Push all changes to GitHub
- Document what you've done
- List remaining work for next session
### Deliverable
- Complete .cursorrules file
- Screenshot van Cursor die regels volgt
- Korte analyse: wat werkt goed, wat niet
---
## 💡 Eindopdracht Check-in
Hoe gaat je eindproject? Loop je ergens tegenaan? Dit is een goed moment om vragen te stellen en feedback te krijgen van de docent en klasgenoten.
- All work committed and pushed
- Documentation current
- Task list for Les 17
---
## Leerdoelen
Na deze les kan de student:
- Het juiste Claude model kiezen per taak
- Composer Mode effectief gebruiken voor multi-file features
- @ mentions strategisch inzetten voor context
- Geavanceerde .cursorrules files schrijven
- Codebase indexing optimaliseren
- Token gebruik monitoren en kosten beheren
- AI-assisted debugging toepassen
- Refactoring uitvoeren met Cursor
- Zelfstandig bugs identificeren en fixen
- Code quality verbeteren
- Performance optimisatie toepassen
- Productie deployment testen en valideren
- Documentatie up-to-date houden

View File

@@ -1,483 +1,207 @@
# Les 17: Vercel AI SDK, Tool Calling & Agents
# Les 17: Eindproject Polish & Code Review
---
## Hoofdstuk
**Deel 4: Advanced AI Features** (Les 13-18)
**Deel 4: Advanced AI & Deployment** (Les 13-18)
## Beschrijving
Bouw AI-powered features in je apps met de Vercel AI SDK. Leer niet alleen chat interfaces bouwen, maar ook hoe AI externe data kan ophalen via Tool Calling en autonome taken kan uitvoeren als Agent.
Finale polish fase van je eindproject. Focus op code review, peer feedback, en laatste verbeteringen voor inlevering. Voorbereiding op presentatie.
---
## Te Behandelen
### Groepsdiscussie (15 min)
Bespreek klassikaal de Cursor .cursorrules ervaringen uit Les 16 - welke regels zorgen voor betere AI output?
Bespreek klassikaal de werkdag voortgang uit Les 16 - wat hebben jullie afgekregen, wat bleek moeilijker dan verwacht?
### Waarom Vercel AI SDK?
### Code Review Checklist
**Het probleem:** Direct API calls naar OpenAI/Anthropic zijn complex:
- Streaming handmatig implementeren
- Error handling
- State management
- Tool calling implementeren
**TypeScript & Code Quality:**
- [ ] No TypeScript errors
- [ ] No `any` types
- [ ] Props properly typed
- [ ] Error handling complete
- [ ] No console.logs in production
**De oplossing:** Vercel AI SDK
- Simpele React hooks (`useChat`, `useCompletion`)
- Built-in streaming
- Provider-agnostic (OpenAI, Anthropic, etc.)
- **Tool calling out-of-the-box**
- **Agent capabilities met `maxSteps`**
**React Best Practices:**
- [ ] No unnecessary re-renders
- [ ] Keys properly set in lists
- [ ] Hooks rules followed
- [ ] Components split logically
- [ ] Prop drilling minimized
**Styling & UX:**
- [ ] Responsive design working
- [ ] Mobile friendly
- [ ] Consistent styling
- [ ] Accessible (alt text, labels, etc.)
- [ ] No visual bugs
**Performance:**
- [ ] Lighthouse > 80
- [ ] Lazy load images
- [ ] Optimize bundles
- [ ] Fast interactions
- [ ] Minimal flickering
---
### Installatie & Setup
### Peer Review Process
```bash
npm install ai @ai-sdk/openai zod
# zod is nodig voor tool parameter validatie
```
**Hoe peer review doen:**
**Environment variable:**
```bash
# .env.local
OPENAI_API_KEY=sk-xxxxx
```
1. **Voorbereiding (10 min)**
- Share productie URL of GitHub link
- List main features
- Highlight AI features
2. **Review (15 min)**
- Reviewer tests alle features
- Takes notes
- Looks at code (if applicable)
3. **Feedback (10 min)**
- ✅ Wat werkt goed
- ⚠️ What could improve
- ❌ Any bugs found
4. **Discussion (5 min)**
- Q&A
- Discuss suggestions
- Agree on priorities
---
### Deel 1: Basic Chat (Herhaling)
### Final Checklist for Submission
#### useChat Hook
**Functionality:**
- [ ] All features work in production
- [ ] Auth flows complete
- [ ] CRUD operations complete
- [ ] AI feature functional
- [ ] No console errors
```tsx
'use client'
import { useChat } from 'ai/react'
**Documentation:**
- [ ] README.md complete
- [ ] PROMPT-LOG.md has 10+ entries
- [ ] AI-DECISIONS.md has 5+ entries
- [ ] .env.example up to date
- [ ] Setup instructions clear
export function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat()
**Code Quality:**
- [ ] Code is clean and organized
- [ ] Comments where needed
- [ ] Consistent naming
- [ ] No dead code
- [ ] .cursorrules present
return (
<div className="flex flex-col h-screen">
<div className="flex-1 overflow-y-auto p-4">
{messages.map(m => (
<div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}>
<span className="inline-block p-2 rounded-lg bg-gray-100">
{m.content}
</span>
</div>
))}
</div>
**Performance & UX:**
- [ ] Lighthouse score > 80
- [ ] Loading states visible
- [ ] Error states handled
- [ ] Mobile responsive
- [ ] Fast load times
<form onSubmit={handleSubmit} className="p-4 border-t">
<input
value={input}
onChange={handleInputChange}
placeholder="Type a message..."
className="w-full p-2 border rounded"
/>
</form>
</div>
)
}
```
#### Basic API Route
```typescript
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai'
import { streamText } from 'ai'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: openai('gpt-4o-mini'),
system: 'You are a helpful assistant.',
messages,
})
return result.toDataStreamResponse()
}
```
---
### Deel 2: Tool Calling - AI + Externe Data
**Het probleem met basic chat:**
- AI kent alleen zijn training data
- Geen toegang tot realtime informatie
- Kan geen acties uitvoeren
**De oplossing: Tool Calling**
- Definieer "tools" die AI kan aanroepen
- AI besluit zelf wanneer een tool nodig is
- Tool haalt data op → AI interpreteert resultaat
#### Voorbeeld: Cocktail Advisor met TheCocktailDB
```typescript
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai'
import { streamText, tool } from 'ai'
import { z } from 'zod'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: openai('gpt-4o-mini'),
system: `Je bent een cocktail expert.
Gebruik de tools om cocktails te zoeken en recepten op te halen.
Geef persoonlijk advies op basis van de resultaten.`,
messages,
tools: {
// Tool 1: Zoek cocktails op ingrediënt
searchByIngredient: tool({
description: 'Zoek cocktails die een specifiek ingrediënt bevatten',
parameters: z.object({
ingredient: z.string().describe('Het ingrediënt om op te zoeken, bijv. "rum" of "vodka"')
}),
execute: async ({ ingredient }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?i=${ingredient}`
)
const data = await res.json()
return data.drinks?.slice(0, 5) || []
}
}),
// Tool 2: Haal cocktail details op
getCocktailDetails: tool({
description: 'Haal het volledige recept van een cocktail op',
parameters: z.object({
cocktailId: z.string().describe('Het ID van de cocktail')
}),
execute: async ({ cocktailId }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${cocktailId}`
)
const data = await res.json()
return data.drinks?.[0] || null
}
}),
// Tool 3: Zoek non-alcoholische opties
searchNonAlcoholic: tool({
description: 'Zoek non-alcoholische cocktails/mocktails',
parameters: z.object({}),
execute: async () => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?a=Non_Alcoholic`
)
const data = await res.json()
return data.drinks?.slice(0, 5) || []
}
})
}
})
return result.toDataStreamResponse()
}
```
**Wat gebeurt er?**
```
User: "Ik heb rum en limoen, wat kan ik maken?"
AI denkt: "Ik moet zoeken op rum"
→ Roept searchByIngredient({ ingredient: "rum" }) aan
→ Krijgt: [{ name: "Mojito", id: "11000" }, { name: "Daiquiri", id: "11006" }, ...]
AI denkt: "Mojito klinkt goed met limoen, laat me het recept ophalen"
→ Roept getCocktailDetails({ cocktailId: "11000" }) aan
→ Krijgt: { name: "Mojito", ingredients: [...], instructions: "..." }
AI antwoordt: "Met rum en limoen kun je een heerlijke Mojito maken!
Je hebt nog nodig: verse munt en suiker..."
```
---
### Deel 3: Agents - Autonome Multi-Step AI
**Van Tool Calling naar Agent:**
- Tool calling = AI roept 1 tool aan, klaar
- Agent = AI blijft tools aanroepen totdat de taak af is
**Het verschil is één parameter: `maxSteps`**
```typescript
const result = streamText({
model: openai('gpt-4o-mini'),
system: `Je bent een cocktail party planner.
Plan een compleet menu met alle details.`,
messages,
tools: { /* ... tools ... */ },
maxSteps: 8 // ← Agent mag 8 tool-calls doen
})
```
#### Voorbeeld: Party Planner Agent
```typescript
// app/api/party-planner/route.ts
import { openai } from '@ai-sdk/openai'
import { streamText, tool } from 'ai'
import { z } from 'zod'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: openai('gpt-4o'), // Gebruik slimmer model voor agent taken
system: `Je bent een professionele cocktail party planner.
Wanneer iemand een feest wil plannen:
1. Zoek eerst cocktails die passen bij de wensen
2. Haal recepten op van de beste opties
3. Denk aan non-alcoholische alternatieven
4. Geef een compleet overzicht met ingrediënten
Wees proactief en denk mee.`,
messages,
tools: {
searchByIngredient: tool({
description: 'Zoek cocktails met een ingrediënt',
parameters: z.object({
ingredient: z.string()
}),
execute: async ({ ingredient }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?i=${ingredient}`
)
return res.json()
}
}),
getCocktailDetails: tool({
description: 'Haal recept details op',
parameters: z.object({
cocktailId: z.string()
}),
execute: async ({ cocktailId }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${cocktailId}`
)
return res.json()
}
}),
searchNonAlcoholic: tool({
description: 'Zoek mocktails',
parameters: z.object({}),
execute: async () => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?a=Non_Alcoholic`
)
return res.json()
}
}),
searchByCategory: tool({
description: 'Zoek cocktails per categorie (Cocktail, Shot, Beer, etc.)',
parameters: z.object({
category: z.string()
}),
execute: async ({ category }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?c=${category}`
)
return res.json()
}
})
},
maxSteps: 10 // Agent kan tot 10 tool calls doen
})
return result.toDataStreamResponse()
}
```
**Agent in actie:**
```
User: "Plan cocktails voor mijn verjaardagsfeest.
15 mensen, een paar drinken geen alcohol,
we houden van citrus smaken."
Agent stappen:
1. searchByIngredient("lemon") → 12 cocktails
2. searchByIngredient("lime") → 15 cocktails
3. searchByIngredient("orange") → 10 cocktails
4. searchNonAlcoholic() → 8 mocktails
5. getCocktailDetails("11000") → Mojito recept
6. getCocktailDetails("11007") → Margarita recept
7. getCocktailDetails("12162") → Virgin Piña Colada recept
8. getCocktailDetails("12316") → Lemonade recept
Output: Compleet party plan met:
- 3 alcoholische cocktails met citrus
- 2 mocktails voor niet-drinkers
- Gecombineerde ingrediëntenlijst
- Tips voor bereiding
```
---
### Gratis APIs voor Projecten
| API | Data | URL | Auth |
|-----|------|-----|------|
| TheCocktailDB | 636 cocktails, recepten | thecocktaildb.com/api.php | Geen (key=1) |
| TheMealDB | 597 recepten, ingrediënten | themealdb.com/api.php | Geen (key=1) |
| Open Trivia DB | 4000+ quiz vragen | opentdb.com/api_config.php | Geen |
| REST Countries | Landen data | restcountries.com | Geen |
| Open Library | Boeken data | openlibrary.org/developers | Geen |
---
### Best Practices
**Tool Design:**
```typescript
// ✅ Goed: Specifieke, duidelijke tools
searchByIngredient: tool({
description: 'Zoek cocktails die een specifiek ingrediënt bevatten',
// ...
})
// ❌ Slecht: Vage tool
search: tool({
description: 'Zoek iets',
// ...
})
```
**Agent System Prompts:**
```typescript
// ✅ Goed: Geef duidelijke instructies
system: `Je bent een cocktail expert.
Wanneer je een vraag krijgt:
1. Zoek eerst relevante cocktails
2. Haal details op van de beste matches
3. Geef persoonlijk advies
Wees proactief en denk mee met de gebruiker.`
// ❌ Slecht: Te vaag
system: `Je bent een assistent.`
```
**Error Handling in Tools:**
```typescript
execute: async ({ ingredient }) => {
try {
const res = await fetch(`...`)
if (!res.ok) {
return { error: 'Kon geen cocktails vinden' }
}
return res.json()
} catch (error) {
return { error: 'API niet beschikbaar' }
}
}
```
**Deployment:**
- [ ] Deployed on Vercel
- [ ] Working on production URL
- [ ] Supabase configured
- [ ] Environment variables secure
- [ ] No errors in production
---
## Tools
- Vercel AI SDK (`ai` package)
- Zod (parameter validatie)
- Next.js API Routes
- Externe APIs (TheCocktailDB, TheMealDB, etc.)
- GitHub
- Vercel
- Chrome DevTools
- Cursor
- Peer reviewers
---
## Lesopdracht (2 uur)
## Lesopdracht (3 uur)
### Bouw een AI Agent met Externe Data
### Code Review & Polish Session
**Deel 1: Setup (15 min)**
1. `npm install ai @ai-sdk/openai zod`
2. Voeg `OPENAI_API_KEY` toe aan `.env.local`
3. Kies je API: TheCocktailDB of TheMealDB
**Deel 1: Peer Review (1 uur)**
**Deel 2: Basic Tool Calling (45 min)**
1. Maak `/api/chat/route.ts`
2. Implementeer 2 tools:
- Zoek op ingrediënt
- Haal details op
3. Test: "Wat kan ik maken met [ingrediënt]?"
Work in pairs or small groups:
1. Exchange project URLs/repos
2. Each person reviews another's work
3. Take detailed notes
4. Provide constructive feedback
5. Discuss improvements
**Deel 3: Agent met maxSteps (45 min)**
1. Voeg `maxSteps: 5` toe
2. Voeg een 3e tool toe (bijv. zoek per categorie)
3. Verbeter je system prompt voor agent gedrag
4. Test: "Help me een menu plannen voor..."
**Deel 2: Final Polish (1.5 uur)**
**Deel 4: Frontend (15 min)**
1. Bouw chat UI met `useChat`
2. Voeg loading indicator toe
3. Test de complete flow
Based on feedback:
1. Fix identified bugs
2. Implement suggested improvements
3. Code cleanup
4. Update documentation
5. Final test in production
**Deel 3: Final Checks (30 min)**
Go through the submission checklist:
1. Verify all items are done
2. Test everything once more
3. Make final commits
4. Push to GitHub
5. Screenshot for documentation
### Deliverable
- Werkende agent met minimaal 3 tools
- Chat interface
- Screenshot van agent die meerdere tools aanroept
- Peer review feedback received
- All feedback items addressed
- Final production-ready code
- Complete documentation
- Screenshot of final app
---
## Huiswerk (2 uur)
## Huiswerk
### Bouw AI Feature voor Eindproject
**Final submission preparation:**
**Deel 1: Agent Design (30 min)**
1. **Complete ALL documentation:**
- README with features and setup
- PROMPT-LOG.md with 10+ prompts
- AI-DECISIONS.md with 5+ decisions
- Project state documented
Plan je agent voor de eindopdracht:
- Welke externe API gebruik je?
- Welke tools heeft je agent nodig? (minimaal 3)
- Wat is de typische flow?
2. **Final testing:**
- Test all features in production
- Check Lighthouse score
- Verify mobile responsiveness
- Check load times
Documenteer in `docs/AI-DECISIONS.md`
3. **Code review:**
- Ask classmates to review code
- Ask docent for feedback
- Fix any issues found
- Final cleanup
**Deel 2: Implementatie (1 uur)**
Bouw de agent voor je eindproject:
- Minimaal 3 tools
- `maxSteps` van minimaal 3
- Goede error handling
- Relevante system prompt
**Deel 3: Integratie (30 min)**
Combineer met Supabase:
- Sla user preferences op
- Geef preferences mee als context aan agent
- Sla conversation history op
4. **Prepare for submission:**
- Ensure Git history is clean
- All commits have good messages
- GitHub repo is public/accessible
- Production URL is stable
### Deliverable
- Werkende agent in eindproject
- `docs/AI-DECISIONS.md` met agent design
- Minimaal 5 prompts in `PROMPT-LOG.md`
---
## 💡 Eindopdracht
Heb je al nagedacht over je AI feature? Dit is het moment om je idee te bespreken met de docent en klasgenoten. Welke externe API ga je gebruiken? Welke tools heeft je agent nodig?
- Final, polished application
- All documentation complete
- Code review completed
- Ready for submission
---
## Leerdoelen
Na deze les kan de student:
- Vercel AI SDK installeren en configureren
- Tools definiëren met Zod parameters
- Tool Calling implementeren voor externe API integratie
- Agents bouwen met `maxSteps` voor autonome taken
- De juiste aanpak kiezen (basic chat vs tool calling vs agent)
- Error handling implementeren in tools
- Gratis externe APIs integreren in AI features
- Code review uitvoeren volgens best practices
- Peer feedback ontvangen en implementeren
- Final polish toepassen op projecten
- Production checklist doorlopen
- Professional quality deliverables opleveren
- Zelfstandig werk evalueren en verbeteren

View File

@@ -3,7 +3,7 @@
---
## Hoofdstuk
**Deel 4: Advanced AI Features** (Les 13-18)
**Deel 4: Advanced AI & Deployment** (Les 13-18)
## Beschrijving
Deploy je eindproject naar productie. Leer environment variables, Vercel deployment, en basis performance optimalisatie. Bespreking van de eindopdracht requirements en afrondende werksessie.