final lessons
This commit is contained in:
212
Les07-Supabase/Les07-Lesopdracht.md
Normal file
212
Les07-Supabase/Les07-Lesopdracht.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# Les 7 — Lesopdracht
|
||||
## Supabase Setup + eerste queries
|
||||
|
||||
**Duur:** ~120 minuten klassikaal
|
||||
|
||||
---
|
||||
|
||||
## Doel
|
||||
|
||||
Aan het einde: Supabase project gekoppeld aan QuickPoll. Polls tabel aangemaakt, en homepage leest uit echte database in plaats van in-memory.
|
||||
|
||||
---
|
||||
|
||||
## Stap 1 — Supabase project klaar
|
||||
|
||||
Account + project moeten klaar zijn (uit Les 6 huiswerk). Anders nu doen.
|
||||
|
||||
Check je gegevens (Settings → API):
|
||||
- Project URL: `https://abc123.supabase.co`
|
||||
- Anon key: `eyJ...`
|
||||
|
||||
---
|
||||
|
||||
## Stap 2 — Tabellen via SQL Editor
|
||||
|
||||
Supabase Dashboard → SQL Editor → New query:
|
||||
|
||||
```sql
|
||||
-- Polls tabel
|
||||
create table polls (
|
||||
id bigserial primary key,
|
||||
title text not null,
|
||||
created_at timestamp default now()
|
||||
);
|
||||
|
||||
-- Options tabel (relatie naar polls)
|
||||
create table options (
|
||||
id bigserial primary key,
|
||||
poll_id bigint not null references polls(id) on delete cascade,
|
||||
text text not null,
|
||||
votes integer default 0,
|
||||
created_at timestamp default now()
|
||||
);
|
||||
|
||||
-- RLS open voor demo
|
||||
alter table polls enable row level security;
|
||||
alter table options enable row level security;
|
||||
|
||||
create policy "polls demo open" on polls
|
||||
for all to anon using (true) with check (true);
|
||||
|
||||
create policy "options demo open" on options
|
||||
for all to anon using (true) with check (true);
|
||||
```
|
||||
|
||||
Run → check Table Editor: beide tabellen verschijnen.
|
||||
|
||||
---
|
||||
|
||||
## Stap 3 — Test data inserten
|
||||
|
||||
In SQL Editor:
|
||||
|
||||
```sql
|
||||
insert into polls (title) values
|
||||
('Beste programmeertaal?'),
|
||||
('Beste IDE?');
|
||||
|
||||
insert into options (poll_id, text) values
|
||||
(1, 'TypeScript'), (1, 'Python'), (1, 'Go'),
|
||||
(2, 'Cursor'), (2, 'VS Code'), (2, 'Vim');
|
||||
```
|
||||
|
||||
Check Table Editor → 2 polls, 6 opties.
|
||||
|
||||
---
|
||||
|
||||
## Stap 4 — Supabase in Next.js
|
||||
|
||||
```bash
|
||||
cd quickpoll
|
||||
pnpm add @supabase/supabase-js
|
||||
```
|
||||
|
||||
`.env.local`:
|
||||
```
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://abc123.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
|
||||
```
|
||||
|
||||
`lib/supabase.ts`:
|
||||
```typescript
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
export const supabase = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stap 5 — Homepage van Supabase
|
||||
|
||||
Refactor `app/page.tsx`:
|
||||
|
||||
```tsx
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import Link from "next/link";
|
||||
|
||||
export default async function Home() {
|
||||
const { data: polls, error } = await supabase
|
||||
.from("polls")
|
||||
.select("*")
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
if (error) return <p className="p-8 text-red-600">Error: {error.message}</p>;
|
||||
|
||||
return (
|
||||
<main className="max-w-2xl mx-auto p-8">
|
||||
<h1 className="text-3xl font-bold mb-6">QuickPoll</h1>
|
||||
<ul className="space-y-2">
|
||||
{polls?.map(p => (
|
||||
<li key={p.id}>
|
||||
<Link href={`/poll/${p.id}`} className="text-blue-600 hover:underline">
|
||||
{p.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Restart `pnpm dev` (env-changes vereisen restart). Open localhost:3000 → polls zichtbaar uit Supabase.
|
||||
|
||||
---
|
||||
|
||||
## Stap 6 — Detail-pagina
|
||||
|
||||
```tsx
|
||||
// app/poll/[id]/page.tsx
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default async function PollPage({
|
||||
params,
|
||||
}: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
|
||||
const { data: poll } = await supabase
|
||||
.from("polls")
|
||||
.select("*, options(*)")
|
||||
.eq("id", Number(id))
|
||||
.single();
|
||||
|
||||
if (!poll) notFound();
|
||||
|
||||
return (
|
||||
<main className="max-w-md mx-auto p-8">
|
||||
<h1 className="text-2xl font-bold mb-4">{poll.title}</h1>
|
||||
<ul className="space-y-2">
|
||||
{poll.options.map(o => (
|
||||
<li key={o.id} className="p-3 border rounded flex justify-between">
|
||||
<span>{o.text}</span>
|
||||
<span className="text-gray-500">{o.votes}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Voting bouwen we in Les 8.
|
||||
|
||||
---
|
||||
|
||||
## Eisen
|
||||
|
||||
- [ ] Supabase project actief met `polls` + `options` tabellen
|
||||
- [ ] RLS aan met open policies
|
||||
- [ ] Minstens 2 polls in DB
|
||||
- [ ] Homepage leest van Supabase
|
||||
- [ ] Detail-pagina toont opties van Supabase
|
||||
- [ ] Geen errors in console
|
||||
|
||||
---
|
||||
|
||||
## Bonus
|
||||
|
||||
- Voeg een 3e tabel `votes` toe (voor user-tracking later)
|
||||
- Type-generation: `pnpm dlx supabase gen types typescript --linked > types/database.ts`
|
||||
- Loading-skeleton voor detail-pagina
|
||||
|
||||
---
|
||||
|
||||
## Veelvoorkomende problemen
|
||||
|
||||
| Probleem | Oplossing |
|
||||
|----------|-----------|
|
||||
| "Invalid API key" | Anon key klopt niet — check .env.local, restart dev |
|
||||
| Polls niet zichtbaar | RLS policy ontbreekt — check beide tabellen |
|
||||
| `.env.local` niet geladen | Restart `pnpm dev` na env-changes |
|
||||
| Foreign key violation | Insert opties na polls (poll_id moet bestaan) |
|
||||
|
||||
---
|
||||
|
||||
## Klaar?
|
||||
|
||||
Volgende les: schrijven naar DB (INSERT, UPDATE) — create-pagina + voting werkend.
|
||||
Reference in New Issue
Block a user