Kurs vibe coding z AI · Moduł 14: Pinterest Clone

Database - Prisma ORM

1 min czytania
W tej lekcji3

Integracja z bazą danych.

Prisma Setup

npm install prisma @prisma/client
npx prisma init

Schema

// prisma/schema.prisma
model User {
  id    Int     @id @default(autoincrement())
  name  String
  email String  @unique
  posts Post[]
}

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  content  String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int
}

Usage

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// Create
await prisma.user.create({
  data: {
    name: 'Jack',
    email: 'jack@pirates.com'
  }
});

// Read
const users = await prisma.user.findMany({
  include: { posts: true }
});

Do zobaczenia!

Kod do tej lekcji: App.tsx
'use client';

import { useState } from 'react';

interface Board {
  id: string;
  name: string;
  pinCount: number;
  coverImage?: string;
}

const mockBoards: Board[] = [
  { id: '1', name: 'Pomysły na podróże', pinCount: 247, coverImage: 'https://images.unsplash.com/photo-1488646953014-85cb44e25828?w=100&h=100&fit=crop' },
  { id: '2', name: 'Przepisy', pinCount: 156, coverImage: 'https://images.unsplash.com/photo-1495521821757-a1efb6729352?w=100&h=100&fit=crop' },
  { id: '3', name: 'Wystrój wnętrz', pinCount: 89, coverImage: 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?w=100&h=100&fit=crop' }
];

const mockPin = {
  title: 'Piękny zachód słońca',
  description: 'Niesamowity zachód słońca nad oceanem w świetle złotej godziny',
  imageUrl: 'https://images.unsplash.com/photo-1544551763-46a013bb70d5?w=100&h=100&fit=crop'
};

export default function SavePinModal() {
  const [isOpen, setIsOpen] = useState(true);
  const [selectedBoardId, setSelectedBoardId] = useState<string | null>(null);

  if (!isOpen) {
    return (
      <div className="min-h-screen bg-gray-100 flex items-center justify-center">
        <button
          onClick={() => setIsOpen(true)}
          className="px-6 py-3 bg-red-600 text-white rounded-full font-semibold"
        >
          Zapisz pin
        </button>
      </div>
    );
  }

  return (
    <div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
      <div className="bg-white rounded-2xl max-w-md w-full max-h-[80vh] flex flex-col">
        {/* Nagłówek */}
        <div className="p-6 border-b border-gray-200">
          <div className="flex items-center justify-between">
            <h2 className="text-xl font-bold">Zapisz na tablicy</h2>
            <button onClick={() => setIsOpen(false)} className="text-gray-500 hover:text-black text-xl">
              ×
            </button>
          </div>
        </div>

        {/* Podgląd pinu */}
        <div className="p-6 border-b border-gray-200">
          <div className="flex gap-3">
            <img src={mockPin.imageUrl} alt={mockPin.title} className="w-16 h-16 rounded object-cover" />
            <div className="flex-1">
              <h3 className="font-semibold">{mockPin.title}</h3>
              <p className="text-sm text-gray-600 line-clamp-2">{mockPin.description}</p>
            </div>
          </div>
        </div>

        {/* Tablice */}
        <div className="flex-1 overflow-y-auto p-6">
          <button className="w-full p-4 border-2 border-dashed border-gray-300 rounded-2xl hover:border-gray-400 mb-4">
            <div className="flex items-center gap-3">
              <div className="w-12 h-12 bg-gray-200 rounded flex items-center justify-center text-2xl">
                +
              </div>
              <span className="font-semibold">Utwórz tablicę</span>
            </div>
          </button>

          {mockBoards.map(board => (
            <button
              key={board.id}
              onClick={() => setSelectedBoardId(board.id)}
              className={`w-full p-4 rounded-2xl hover:bg-gray-100 mb-2 ${
                selectedBoardId === board.id ? 'bg-gray-100 ring-2 ring-blue-500' : ''
              }`}
            >
              <div className="flex items-center gap-3">
                {board.coverImage ? (
                  <img src={board.coverImage} alt={board.name} className="w-12 h-12 rounded object-cover" />
                ) : (
                  <div className="w-12 h-12 bg-gray-200 rounded" />
                )}
                <div className="flex-1 text-left">
                  <div className="font-semibold">{board.name}</div>
                  <div className="text-sm text-gray-600">{board.pinCount} pinów</div>
                </div>
                {selectedBoardId === board.id && <div className="text-blue-500 text-xl">√</div>}
              </div>
            </button>
          ))}
        </div>

        {/* Przycisk zapisu */}
        <div className="p-6 border-t border-gray-200">
          <button
            disabled={!selectedBoardId}
            className="w-full py-3 bg-red-600 text-white rounded-full font-semibold hover:bg-red-700 disabled:bg-gray-300"
          >
            Zapisz
          </button>
        </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. 1. Czym jest Prisma w aplikacji?

  2. 2. Jak w findMany pobrać powiązane rekordy (np. posty użytkownika)?

Zadania praktyczne w grze

  • Klikanie w kolejności

    Kliknij elementy, aby zbudować komendę instalacji Prisma:

  • Edytor kodu

    Stwórz model i zapytania Prisma

  • Układanie w pionie

    Uporządkuj etapy pracy z bazą przez Prisma:

Przydatne artykuły