8.0 KiB
Les 7: Next.js Fundamentals 2 - API Routes & Data Fetching
Hoofdstuk
Deel 2: Technical Foundations (Les 5-9)
Beschrijving
Leer data fetching in Next.js: Server Components, Client Components, API routes en React Query. Bouw een volledig werkende app met data.
Te Behandelen
Server Components vs Client Components
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
Client Components:
- Renderen in de browser
- JavaScript naar de browser
- Kunnen interactief zijn
- Markeer met
'use client'bovenaan
Wanneer Wat?
Server Component → Data tonen, geen interactie
Client Component → Interactie nodig (forms, buttons, state)
Voorbeeld:
// Server Component - data ophalen
async function ProductList() {
const products = await fetchProducts() // Direct fetchen!
return <ul>{products.map(p => <li>{p.name}</li>)}</ul>
}
// Client Component - interactie
'use client'
function AddToCartButton({ productId }) {
const [added, setAdded] = useState(false)
return <button onClick={() => setAdded(true)}>Add</button>
}
Data Fetching in Server Components
Simpelweg async/await gebruiken:
// app/products/page.tsx
interface Product {
id: number
name: string
price: number
}
async function getProducts(): Promise<Product[]> {
const res = await fetch('https://api.example.com/products')
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
return (
<div>
<h1>Producten</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - €{product.price}
</li>
))}
</ul>
</div>
)
}
API Routes (Route Handlers)
Bouw je eigen API in Next.js:
Folder structuur:
app/
└── api/
└── products/
└── route.ts → /api/products
GET request (app/api/products/route.ts):
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:
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:
'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:
npm install @tanstack/react-query
Setup Provider (app/providers.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:
import { Providers } from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
useQuery - Data Ophalen
'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
'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
// app/products/page.tsx (Server Component)
import { ProductList } from './product-list'
import { AddProductForm } from './add-product-form'
export default function ProductsPage() {
return (
<div>
<h1>Producten</h1>
<AddProductForm /> {/* Client Component */}
<ProductList /> {/* Client Component */}
</div>
)
}
Tools
- Next.js 14
- React Query (TanStack Query)
- TypeScript
- OpenCode/WebStorm
Lesopdracht (2 uur)
Bouw CRUD App met API Routes
Deel 1: API Routes (40 min)
- Maak
app/api/products/route.ts - Implementeer GET (alle producten)
- Implementeer POST (product toevoegen)
- Test met browser/Postman:
/api/products
Deel 2: React Query Setup (20 min)
- Installeer
@tanstack/react-query - Maak
app/providers.tsx - Wrap app in
QueryClientProvider
Deel 3: Data Tonen met useQuery (30 min)
- Maak
ProductListClient Component - Gebruik
useQueryom data te fetchen - Toon loading state
- Toon error state
- Render product lijst
Deel 4: Data Toevoegen met useMutation (30 min)
- Maak
AddProductFormClient Component - Gebruik
useMutationvoor POST - Invalidate query na success
- Toon "Adding..." state
Deliverable
- Werkende API routes (GET, POST)
- ProductList met useQuery
- AddProductForm met useMutation
- Loading en error states
Huiswerk (2 uur)
Volledige CRUD Interface
Deel 1: PUT en DELETE Routes (45 min)
- Maak
app/api/products/[id]/route.ts - Implementeer PUT (update product)
- Implementeer DELETE (verwijder product)
- Test beide endpoints
Deel 2: Update Functionaliteit (45 min)
- Maak edit form in ProductList
- Gebruik useMutation voor PUT
- Inline editing OF modal
- Invalidate query na success
Deel 3: Delete Functionaliteit (30 min)
- Voeg delete button toe per product
- Gebruik useMutation voor DELETE
- Voeg confirmation dialog toe
- Invalidate query na success
Bonus: Optimistic Updates
- Product direct uit UI verwijderen
- Rollback als server faalt
Deliverable
- Complete CRUD API (GET, POST, PUT, DELETE)
- UI voor alle operaties
- Error handling
- Optimistic updates (bonus)
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