Vibe coding with AI course · Module 14: Pinterest Clone
API Routes - REST & GraphQL
In this lesson2
Creating 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; // since Next.js 15 params is a Promise
const user = await prisma.user.findUnique({
where: { id: parseInt(id) }
});
return Response.json({ user });
}See you there!
Code for this lesson: App.tsx
'use client';
import { useState } from 'react';
const relatedSearches = ['pirate ship', 'sunset photography', 'ocean waves', 'sailing', 'nautical design'];
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">Search by image</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">Upload an image</div>
<div className="text-sm text-gray-600">or drag and drop</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">
{/* Image Preview */}
<div className="mb-8">
<img src={imagePreview} alt="Search query" className="max-w-sm mx-auto rounded-2xl shadow-lg" />
</div>
{/* Loading */}
{isSearching ? (
<div className="text-center text-gray-600 mb-8 text-lg">
Searching for similar pins...
</div>
) : (
<>
{/* Related Searches */}
<div className="mb-8">
<h2 className="text-2xl font-bold mb-4">Related searches</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>
{/* Similar Pins */}
<div>
<h2 className="text-2xl font-bold mb-4">Similar pins</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>
);
}Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. How do you create an endpoint responding to GET in the Next.js App Router?
2. Where does the handler in app/api/users/[id]/route.ts get the id from the URL?
Hands-on tasks in the game
- Click in order
Click the elements to build returning a list of users as JSON:
- Horizontal ordering
Arrange the elements of one line returning the created record with status 201:
- Code editor
Create REST API endpoints