Files
novi-lessons/Les17-Externe-APIs/Les17-Slide-Overzicht.md
2026-06-03 16:58:25 +02:00

419 lines
11 KiB
Markdown

# Les 17 — Externe APIs in diepte
## Slide Overzicht (Klas A — 3 uur fysiek, demo-driven)
**Lesvorm:** Tim demonstreert klassikaal. Studenten kijken. Zelf bouwen = huiswerk.
**Demo-app:** Subscription-dashboard (Stripe Checkout + Resend email)
**Vervolg op:** Les 16 — MCP servers
**Aansluit op:** Les 18 — Supabase Auth + RLS
---
## Slide 1: Title
### Les 17 — Externe APIs in diepte
**Visual:** "Les 17" BLUE, "Externe APIs in diepte" BLACK, subtitle "OAuth, webhooks, Stripe en Resend — productie-patronen"
---
## Slide 2: Terugblik
### Waar staan we?
**Lessen 11-16:**
- AI SDK, tool calling, agents, RAG, Cursor+Vercel deploy, MCP
**Externe APIs deden we al in Les 15 (PokéAPI):** simpel, geen key, GET requests. Productie is anders.
**Vandaag — de echte wereld:**
- Login met Google/GitHub (OAuth)
- Iemand doet een betaling — server moet dat weten (webhooks)
- Betaalde APIs zoals Stripe en Resend
- Wat als de API faalt? Retry-logic
- Hoe doe je dat veilig en netjes?
---
## Slide 3: Planning
### Vandaag — 180 minuten
| Onderwerp | Duur |
|-----------|------|
| Terugblik + API-types | 15 min |
| Theorie: OAuth basics | 20 min |
| Theorie: Webhooks + verifying | 20 min |
| **Live Demo 1** — Stripe Checkout integratie | 25 min |
| **Live Demo 2** — Webhook ontvangen + verify | 25 min |
| **Pauze** | 15 min |
| **Live Demo 3** — Resend transactional email | 25 min |
| **Live Demo 4** — Rate limit + retry pattern | 15 min |
| Productie checklist | 10 min |
| Lesopdracht + Huiswerk | 10 min |
---
## Slide 4: Drie soorten externe API integratie
### Welke heb je nodig?
| Type | Voorbeeld | Wat moet je weten |
|------|-----------|-------------------|
| **Simple fetch** | PokéAPI, Open-Meteo | URL + JSON parsing (Les 15) |
| **API key in header** | OpenAI, Tavily, Anthropic | Env vars, server-side fetch |
| **OAuth flow** | Google login, GitHub | Redirect dance, tokens, sessies |
| **Webhook** | Stripe, GitHub, Slack | POST endpoint, signature verify |
**Vandaag focus op de laatste twee** — daar komen 80% van productie-API-bugs vandaan.
---
## Slide 5: OAuth — wat en waarom
### Login met Google / GitHub / etc.
**Het probleem:** je wilt dat gebruikers inloggen zonder zelf wachtwoorden te beheren.
**OAuth flow (kort):**
```
1. User klikt "Login met GitHub"
2. Redirect naar github.com/login/oauth/authorize
3. User logt in op GitHub + geeft toestemming
4. GitHub redirect terug naar JOUW app met code
5. JOUW server exchanged code voor access_token
6. JOUW server haalt user-data op met token
7. JOUW server zet sessie / cookie
```
**In Next.js — drie populaire libraries:**
- **Auth.js (NextAuth v5)** — meest gebruikt
- **Better-Auth** — moderne challenger, eenvoudiger
- **Clerk** — paid, alles inclusief
Voor dit vak: Auth.js — open source, gratis, werkt overal.
---
## Slide 6: Webhooks — wat en waarom
### Externe service belt JOUW app
**Het probleem:** soms moet je weten wanneer iets gebeurt op een externe service.
**Voorbeeld Stripe:**
- User doet betaling op Stripe Checkout
- Stripe verwerkt betaling (zou kunnen falen!)
- Stripe stuurt POST request naar JOUW webhook URL
- Jouw server update database, stuurt email, etc.
**Webhook = jouw HTTP endpoint** dat door externe service wordt aangeroepen.
**Cruciaal: signature verification**
Zonder verificatie kan iedereen je webhook URL aanroepen met fake data.
```typescript
// Stripe doet dit zo:
const sig = request.headers.get("stripe-signature");
const event = stripe.webhooks.constructEvent(
body, sig, process.env.STRIPE_WEBHOOK_SECRET
);
// Throws als signature niet klopt
```
---
## Slide 7: Wat we vandaag bouwen
### Subscription-dashboard
**Mini-app:** Premium subscription met Stripe + email confirmaties.
**Tech:**
- Next.js 16
- Stripe (test mode — geen echt geld)
- Resend (transactional email — gratis tier 3000/maand)
- ngrok of `localhost.run` voor lokaal webhook testen
**Flow:**
```
[Pricing page] → [Stripe Checkout] → [Webhook] → [Database + Email]
```
**Endpoints we bouwen:**
- `POST /api/checkout` — start Stripe sessie
- `POST /api/webhook/stripe` — ontvang Stripe events
- `POST /api/email` — verzend Resend mail (intern aangeroepen)
---
## Slide 8: LIVE DEMO 1 — Stripe Checkout
### ~25 min
**Wat ik laat zien:**
1. Stripe account opzetten (test mode)
2. Pricing page met "Subscribe" button
3. `app/api/checkout/route.ts` — maak Checkout session
```typescript
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const { priceId, email } = await req.json();
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price: priceId, quantity: 1 }],
customer_email: email,
success_url: `${process.env.APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.APP_URL}/pricing`,
});
return Response.json({ url: session.url });
}
```
4. Client redirect naar `session.url`
5. Test in browser met Stripe test cards (4242 4242 4242 4242)
6. Stripe Dashboard — sessie zichtbaar, betaling staat als "complete"
---
## Slide 9: LIVE DEMO 2 — Webhook ontvangen
### ~25 min
**Wat ik laat zien:**
1. Webhook secret ophalen van Stripe Dashboard
2. `app/api/webhook/stripe/route.ts`:
```typescript
import { headers } from "next/headers";
export async function POST(req: Request) {
const body = await req.text(); // RAW body, niet json()
const sig = headers().get("stripe-signature")!;
let event;
try {
event = stripe.webhooks.constructEvent(
body, sig, process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
return new Response("Webhook signature failed", { status: 400 });
}
if (event.type === "checkout.session.completed") {
const session = event.data.object;
await supabase.from("subscriptions").insert({
customer_email: session.customer_email,
stripe_session_id: session.id,
status: "active",
});
}
return Response.json({ received: true });
}
```
3. **Lokaal testen:** `stripe listen --forward-to localhost:3000/api/webhook/stripe`
4. Stripe CLI geeft webhook secret terug — in `.env.local`
5. Doe een test-checkout — zie webhook binnenkomen in CLI én database update
**Belangrijk:** `req.text()` voor RAW body — niet `req.json()`. Signature wordt over bytes berekend.
---
## Slide 10: Pauze
### 15 min
---
## Slide 11: LIVE DEMO 3 — Resend email
### ~25 min
**Wat ik laat zien:**
1. Resend account, API key, eigen domain (of `onboarding@resend.dev` voor demo)
2. `pnpm add resend react-email`
3. Email template als React component:
```tsx
// emails/WelcomeEmail.tsx
export function WelcomeEmail({ name }: { name: string }) {
return (
<div style={{ fontFamily: "sans-serif" }}>
<h1>Welkom {name}!</h1>
<p>Bedankt voor je aanmelding bij Premium.</p>
</div>
);
}
```
4. Verzend in webhook handler:
```typescript
import { Resend } from "resend";
import { WelcomeEmail } from "@/emails/WelcomeEmail";
const resend = new Resend(process.env.RESEND_API_KEY!);
await resend.emails.send({
from: "onboarding@resend.dev",
to: session.customer_email!,
subject: "Welkom bij Premium!",
react: WelcomeEmail({ name: session.customer_email!.split("@")[0] }),
});
```
5. Test checkout → webhook → mail komt aan
6. Resend dashboard — email zichtbaar in logs, status delivered
---
## Slide 12: LIVE DEMO 4 — Rate limit + retry
### ~15 min
**Wat ik laat zien:**
**Probleem:** externe APIs kunnen falen. Wat doe je?
**Pattern 1 — Exponential backoff retry:**
```typescript
async function retry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
if (i === attempts - 1) throw err;
await new Promise(r => setTimeout(r, 2 ** i * 1000));
}
}
throw new Error("unreachable");
}
const data = await retry(() =>
fetch("https://api.example.com/data").then(r => r.json())
);
```
**Pattern 2 — Rate limit met `Upstash Ratelimit`:**
```typescript
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10s"),
});
// In API route
const { success } = await ratelimit.limit(userId);
if (!success) return new Response("Too many requests", { status: 429 });
```
Beide patronen zijn cruciaal voor productie. Niet doen = service crash bij verkeerd gedrag.
---
## Slide 13: Productie checklist
### Voordat je live gaat
**API keys:**
- ✓ Test-keys voor preview, prod-keys voor productie
- ✓ Vercel scoping per environment (Les 15)
- ✓ Rotate keys eens per kwartaal
**Webhooks:**
- ✓ Signature verification ALTIJD
- ✓ Idempotency — zelfde event 2x ontvangen = OK
- ✓ Log alle webhook payloads (helpt bij debug)
- ✓ Return 200 snel — verwerking async
**Errors:**
- ✓ Exponential backoff retry voor transient fails
- ✓ Rate limit eigen endpoints
- ✓ Monitor met Sentry / LogRocket / Vercel Analytics
**Security:**
- ✓ Keys nooit in client-bundle (geen `NEXT_PUBLIC_`)
- ✓ Webhook endpoints accepteren alleen verified signatures
- ✓ User-input validatie (Zod)
---
## Slide 14: Lesopdracht + Huiswerk
### Eigen externe API integreren
**Lesopdracht (30 min):**
- Stripe test account opzetten
- Pricing page + Checkout endpoint
- Doe een test-betaling met test card
- Geen webhook nog — alleen success_url demo
**Huiswerk (~2 uur, voor Les 18):**
- A: Webhook handler + signature verify
- B: Resend transactional email na betaling
- C: Retry logic voor externe API call
- D: `APIS.md` met:
- Welke externe APIs jouw app gebruikt
- Hoe webhooks getest worden (Stripe CLI of ngrok screenshots)
- Voorbeeld van een failed call + hoe afgevangen
- Eindopdracht-toepassing: welke API ga jij integreren
**Bonus:** OAuth login met GitHub/Google via Auth.js
---
## Slide 15: Volgende les + Afsluiting
### Vragen?
**Vandaag gezien:**
- Drie soorten externe API integratie
- OAuth flow basics
- Webhooks ontvangen + signature verify
- Stripe Checkout in productie-patroon
- Resend transactional email
- Rate limit + retry
- Productie checklist
**Volgende les (Les 18 — laatste!): Supabase Auth + RLS**
- Multi-user apps met login
- Magic link / password / social
- RLS policies — per-user data isolation
- Protected routes + middleware
- Combo van alles wat we geleerd hebben
**Vragen?**
---
## Slide Summary
| # | Title | Type |
|---|-------|------|
| 1 | Title | Opening |
| 2 | Terugblik | Recap |
| 3 | Planning | 180-min |
| 4 | 3 soorten API integratie | Theorie |
| 5 | OAuth basics | Theorie |
| 6 | Webhooks + verifying | Theorie |
| 7 | Wat we bouwen | Intro |
| 8 | **DEMO 1** — Stripe Checkout | Demo |
| 9 | **DEMO 2** — Webhook ontvangen | Demo |
| 10 | Pauze | Break |
| 11 | **DEMO 3** — Resend email | Demo |
| 12 | **DEMO 4** — Rate limit + retry | Demo |
| 13 | Productie checklist | Reflectie |
| 14 | Lesopdracht + Huiswerk | Praktijk |
| 15 | Afsluiting | Closing |
---
## Bronnen
- **Stripe Checkout docs:** https://docs.stripe.com/checkout
- **Stripe webhooks:** https://docs.stripe.com/webhooks
- **Stripe CLI:** https://docs.stripe.com/stripe-cli
- **Resend:** https://resend.com/docs
- **React Email:** https://react.email/
- **Auth.js:** https://authjs.dev/
- **Upstash Ratelimit:** https://upstash.com/docs/oss/sdks/ts/ratelimit
- **Better-Auth:** https://www.better-auth.com/