fix: update lessons
This commit is contained in:
418
Les17-Externe-APIs/Les17-Slide-Overzicht.md
Normal file
418
Les17-Externe-APIs/Les17-Slide-Overzicht.md
Normal file
@@ -0,0 +1,418 @@
|
||||
# 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/
|
||||
Reference in New Issue
Block a user