Kurs vibe coding z AI · Moduł 14: Pinterest Clone
API Routes - REST & GraphQL
W tej lekcji2
Tworzenie API endpoints.
REST API
// app/api/users/route.ts
export async function GET() {
const users = await prisma.user.findMany();
return Response.json({ users });
}
export async function POST(request: Request) {
const body = await request.json();
const user = await prisma.user.create({ data: body });
return Response.json({ user }, { status: 201 });
}Dynamic Routes
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params; // od Next.js 15 params to Promise
const user = await prisma.user.findUnique({
where: { id: parseInt(id) }
});
return Response.json({ user });
}Do zobaczenia!
Kod do tej lekcji: App.tsx
'use client';
import { useState } from 'react';
const relatedSearches = ['piracki statek', 'fotografia zachodu słońca', 'fale oceanu', 'żeglarstwo', 'styl marynistyczny'];
export default function CameraSearch() {
const [imagePreview, setImagePreview] = useState<string | null>(null);
const [isSearching, setIsSearching] = useState(false);
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
setImagePreview(event.target?.result as string);
setIsSearching(true);
setTimeout(() => setIsSearching(false), 2000);
};
reader.readAsDataURL(file);
};
if (!imagePreview) {
return (
<div className="min-h-screen bg-white p-8">
<div className="max-w-4xl mx-auto text-center">
<h1 className="text-4xl font-bold mb-8">Szukaj obrazem</h1>
<label className="inline-block cursor-pointer">
<div className="w-64 h-64 border-4 border-dashed border-gray-300 rounded-2xl flex flex-col items-center justify-center hover:border-red-500 transition mx-auto">
<div className="text-6xl mb-4">◆</div>
<div className="font-semibold mb-2">Prześlij obraz</div>
<div className="text-sm text-gray-600">lub przeciągnij i upuść</div>
</div>
<input type="file" accept="image/*" onChange={handleImageUpload} className="hidden" />
</label>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-white p-8">
<div className="max-w-6xl mx-auto">
{/* Podgląd obrazu */}
<div className="mb-8">
<img src={imagePreview} alt="Obraz do wyszukania" className="max-w-sm mx-auto rounded-2xl shadow-lg" />
</div>
{/* Ładowanie */}
{isSearching ? (
<div className="text-center text-gray-600 mb-8 text-lg">
Szukam podobnych pinów...
</div>
) : (
<>
{/* Powiązane wyszukiwania */}
<div className="mb-8">
<h2 className="text-2xl font-bold mb-4">Powiązane wyszukiwania</h2>
<div className="flex flex-wrap gap-2">
{relatedSearches.map(search => (
<button
key={search}
className="px-4 py-2 bg-gray-200 rounded-full hover:bg-gray-300 font-semibold"
>
{search}
</button>
))}
</div>
</div>
{/* Podobne piny */}
<div>
<h2 className="text-2xl font-bold mb-4">Podobne piny</h2>
<div className="grid grid-cols-4 gap-4">
{[1, 2, 3, 4, 5, 6, 7, 8].map(i => (
<div key={i} className="aspect-[3/4] bg-gray-200 rounded-2xl" />
))}
</div>
</div>
</>
)}
</div>
</div>
);
}Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jak w Next.js App Router stworzyć endpoint odpowiadający na GET?
2. Skąd handler w app/api/users/[id]/route.ts bierze id z URL?
Zadania praktyczne w grze
- Klikanie w kolejności
Kliknij elementy, aby zbudować zwrócenie listy użytkowników jako JSON:
- Układanie w poziomie
Ułóż elementy jednej linii zwracającej utworzony rekord ze statusem 201:
- Edytor kodu
Stwórz REST API endpoints