Blog
2025-11-17 - Artur Wilczek
GitHub Copilot - MCP - Supabase - Nuxt

GitHub Copilot - MCP - Supabase - Nuxt

Szybkie (w dwa dni robocze) przygotowanie prototypu / makiety / MVP aplikacji

Po co prototyp lub MVP?

MVP - Minimum Viable Product - najprostsza, funkcjonalna wersja aplikacji, która zawiera jedynie podstawowe funkcje pozwalające rozwiązać kluczowy problem użytkowników. Pojęcie popularne w świecie SaaS, ale ma też zastosowanie przy podejściu do tworzenia aplikacji z szybkim prototypowaniem.

Z doświadczenia wiem, że nawet po wielu godzinach spotkań z przyszłym użytkownikiem aplikacji, przygotowywania koncepcji i architektury rozwiązania, kluczowe pomysły i najwięcej uwag do produktu pojawia się po oddaniu aplikacji do testów, a nawet częściej po oddaniu aplikacji do użytku.

Dzisiaj mamy do dyspozycji narzędzia i technologie, które pozwalają, w bardzo krótkim czasie, przygotować makietę aplikacji, w której użytkownik może wykonywać lub symulować rzeczywiste operacje biznesowe, zgodne z założeniami procesu, który ta aplikacja ma obsługiwać i wspierać. W ten sposób użytkownik bierze czynny udział w procesie tworzenia, a wszystkie zgłaszane rozwiązania lub modyfikację mogę zostać sprawnie zaimplementowane i oddane do kolejnych testów.

Jeśli pracujemy dla nowego, zewnętrznego klienta, który jeszcze nie do końca nam ufa i nie chce ponosić dużych kosztów na początku współpracy, możemy szybko zdefiniować główne założenia projektowe, równie szybko przygotować prototyp i bez ingerencji w jego infrastrukturę, udostępnić prototyp na własnej platformie (jeśli takową dysponujemy) lub skorzystać z jednej z wielu publicznie dostępnych platform hostingowych. W przypadku platform Internetowych, makietę aplikacji błyskawicznie wypełniamy fikcyjnymi i sztucznie wygenerowanymi danymi, nie narażając klienta na wyciek poufnych danych biznesowych.

Co udało się zrobić w tak krótkim czasie?

Na potrzeby tego artykułu poświęciłem dwa dni robocze, żeby przygotować pokazowy prototyp aplikacji, która z założenia może pracować w rejestracji lub sekretariacie placówki medycznej. W tak krótkim czasie udało się wdrożyć funkcje takie jak:

  • lista specjalizacji medycznych
  • dodawanie nowych lekarzy
  • prezentacja lekarzy w formie tabeli z możliwością sortowania/filtrowania
  • dodawanie nowych pacjentów
  • prezentacja pacjentów w formie tabeli z możliwością sortowania/filtrowania
  • definiowanie dostępności lekarzy
  • rezerwowanie wizyt pacjentów
  • wizualizacja danych związanych z lekarzem: dane podstawowe, dostępność, wizyty zaplanowane i archiwalne, pacjenci powiązani relacją pacjent-lekarz rodzinny, rejestr zdarzeń
  • wizualizacja danych związanych z pacjentem: dane podstawowe, wizyty zaplanowane i archiwalne, rejestr zdarzeń

Do końca roku aplikacja pokazowa jest dostępna pod adresem: https://proto-app-001.vercel.app

Zachęcam do utworzenia konta i sprawdzenia jak wyglądają i działają aplikacje tworzone w technologiach, których ostatnio najczęściej używam.

Na końcu tego artykułu udostępniam materiał wideo, na którym demonstruję co potrafi prototyp.

Jak to w ogóle możliwe w tak krótkim czasie?

Takie możliwości dają oczywiście duże modele językowe, z których, coraz sprawniej, korzystają agenci / asystenci AI dedykowani do wykonywania prac programistycznych. Osobiście używam GitHub Copilot Pro zintegrowanego z VS Code.

Stosując tradycyjne techniki programistyczne, nie jestem w stanie dostarczyć prototypu w tak krótkim czasie. Mogę zaryzykować stwierdzenie, że tak samo trudno byłoby uzyskać podobny efekt stosują platformę typu low-code, w której, co prawda, buduje się szybko podstawowe funkcjonalności z gotowych klocków, ale przy implementacji bardziej skomplikowanej logiki biznesowej, na drodze stają ograniczenia lub specyfika platformy.

Z pomocą przychodzą też coraz bardziej popularne protokół i serwery MCP, które dają agentowi AI dostęp, na przykład do:

  • aktualnej dokumentacji narzędzi lub technologii, których używam w czasie przygotowywania prototypu
  • narzędzi platform bazodanowych, które pozwalają kreować obiekty DB stosując język naturalny

W tym projekcie korzystałem z trzech serwerów MCP:

  • dokumentacja Nuxt
  • dokumentacja Nuxt UI
  • narzędzia platformy bazodanowej Supabase (PostgreSQL w chmurze)

Konfiguracja w VS Code jest prosta i wymaga przygotowania pliku mcp.json w folderze .vscode:

.vscode\mcp.json
{
  "servers": {
    "NUXT UI": {
      "type": "http",
      "url": "https://ui.nuxt.com/mcp"
    },

    "NUXT": {
      "type": "http",
      "url": "https://nuxt.com/mcp"
    },

    "SUPABASE": {
      "type": "http",
      "url": "https://mcp.supabase.com/mcp?project_ref=klnuspxlvrxjhudrxcro"
    }
  }
}

Czy faktycznie zaczynałem od zera?

Jeśli chodzi o platformę Supabase, to rzeczywiście tak - utworzyłem nowy, pusty projekt. Wszystkie obiekty DB, takie jak: tabele, polityki RLS, funkcje, triggery, funkcje typu edge, tworzyłem wydając agentowi polecenia w języku naturalnym. To samo jeśli chodzi o wypełnianie tabel fikcyjnymi danymi.

Jeśli chodzi o frontend web-owy, to na start użyłem własnego szablonu, który jest minimalnym szkieletem interfejsu użytkownika i zawiera tylko górną belkę, lewy panel menu (pusty), główną przestrzeń roboczą (pustą), okno logowania i rejestracji użytkownika oraz podstawową integrację z platformą Supabase.

Na czym polegała moja praca?

Głównie na wymyślaniu architektury, definiowaniu zadań dla mojego agenta oraz weryfikowaniu i testowaniu wyników jego pracy.

Wszystkie polecenia dla agenta zapisywałem w notatkach i uzbierało się tego około 100 zadań, co przy dwóch dniach (960 minut) intensywnych prac, daje średnio ponad 9,5 minuty na jedno zadania. Zdecydowana większość promptów była jednozdaniowymi, precyzyjnymi poleceniami, których napisanie zajmowało mniej niż 1 minutę, wykonanie agenta to 2-3 minuty, reszta to była moja weryfikacja i szybkie testy. Zdarzały się też bardziej rozbudowane polecenia, które, na przykład, definiowały skomplikowaną logikę i nie miało sensu dzielić ich na mniejsze porcje, co dawało asystentowi lepsze trzymanie kontekstu. Tak na marginesie, agent sam dzielił większe zadanie na mniejsze podzadania oraz ustalał prawidłową kolejność ich realizacji.

Poniżej kilka ciekawszych zadań / promptów, zarówno tych dla bazy danych Supabase oraz frontend-u Nuxt / Nuxt UI.

Na potrzeby artykułu i aplikacji pokazowej, stosuję polskie nazwy tabel i pól. W realnym świecie raczej stosuje się nazewnictwo w języku angielskim.

Utworzenie dwóch tabel w DB, zdefiniowanie relacji oraz INSERT testowych danych:

Potrzebuję w bazie danych nowej tabeli:

  • nazwa tabeli: lekarze
  • pola: id, imie (text, not null), nazwisko (text, not null)
  • włącz politykę RLS: SELECT, INSERT, UPDATE dla zalogowanych użytkowników

Potrzebuję w bazie danych nowej tabeli:

  • nazwa: specjalizacje
  • pola: id, specjalizacja (text, not null), is_active (boolean, domyślnie true)
  • włącz politykę RLS: tylko SELECT dla zalogowanych użytkowników
  • wypełnij tabelę 30 specjalizacjami lekarskimi

Do tabeli lekarze dodaj pole id_specjalizacji jako klucz obcy do tabeli specjalizacje.

Dodaj 100 lekarzy do tabeli lekarze z różnych specjalizacji.

Rozbudowa strony z tabelą specjalizacji:

Strona /specjalizacje:

  • w kolumnie Status wartość prezentuje komponent UBadge
  • zostaw go, ale zrób z niego link, którym można zmienić (toogle) wartość w polu is_active tabeli specjalizacje
  • obsługa zmiany wartości, komunikaty podobnie jak na stronie /lekarze
  • dodaj możliwość sortowania po kolumnie Status

Nowa strona ze szczegółami lekarza:

Nowa strona /lekarze/:id:

  • dodaj stronę
  • w tabeli lekarzy na stronie /lekarze jest kolumna ID - zrób z niej link do nowej strony
  • na stronie formularz read-only z danymi lekarza
  • poniżej formularza, tabela zdarzeń związanych z lekarzem
  • tabela zdarzeń w prostej formie, sortowanie po dacie malejąco, bez możliwości sortowanie i filtrowania

Nie działają linki do nowej strony. Może trzeba plik lekarze.vue zmienić na index.vue w folderze lekarze?

Korekta formularza dodawania nowego lekarza:

Zmień układ formularza tak aby pola pojawiały się na przemiennie w dwóch kolumnach o tej samej szerokości (użyj grid). Pola typu input czy select powinny zajmować całą szerokość kolumny. Wszystkie błędy i sukcesy pokazuj za pomocą toast. Rozbuduj walidację: z listy lekarzy rodzinnych musi być wybrana pozycja. Lista rozwijana lekarza rodzinnego jest bardzo długa. Dodaj możliwość filtrowania po nazwisku.

Nowa tabela z mechanizmem seed:

Nowa tabela w bazie danych:

  • nazwa tabeli: dni
  • pola: id, dzien (text, not null, unique)
  • włącz politykę RLS: tylko SELECT dla zalogowanych użytkowników
  • utwórz mechanizm seed dla tabeli, tak żeby wstawiał lub aktualizował następujące rekordy:
    • id: 1; dzien: Poniedziałek
    • id: 2; dzien: Wtorek
    • id: 3; dzien: Środa
    • id: 4; dzien: Czwartek
    • id: 5; dzien: Piątek
  • Mechanizm seed tabeli dni zrób jako funkcja bazy danych podobnie jak funkcja seed_event_types.

Funkcja brzegowa realizująca logikę planowania wizyty lekarskiej:

Nowa funkcja brzegowa (edge function):

  • nazwa: zaplanuj_wizyte
  • argumenty: lekarz_id, pacjent_id, dzien

Logika funkcji zaplanuj_wizyte:

  • sprawdź, czy wywołanie funkcji jest w kontekście zalogowanego użytkownika, jeśli nie to błąd
  • sprawdź, czy istnieje lekarz o lekarz_id, jeśli nie to błąd
  • sprawdź, czy istnieje pacjent o pacjent_id, jeśli nie to błąd
  • dzień może być dzisiaj lub w przyszłości, jeśli nie to błąd
  • na podstawie daty ustal jaki to dzień tygodnia (Poniedziałek to 1)
  • sprawdź, czy istnieje taki dzień w tabeli dni, jeśli nie to błąd
  • sprawdź, czy lekarz ma ten dzień tygodnia ustaloną dostępność w tabeli dostepnosc_lekarza, jeśli nie to błąd
  • odczytaj wszystkie rekordy z tabeli wizyty dla lekarza na tę datę posortowane rosnąco po godzinie
  • ustal pierwszą godzinę dostępności lekarza bazując na zdefiniowanej dostępności w dostępnosc_lekarza, biorąc pod uwagę godzinę startową oraz czas potrzebny na pacjenta (pole czas_na_pacjenta); jeśli nie udało się ustalić wolnej godziny (np. z powodu braku miejsc) to błąd
  • dodaj rekord do tabeli wizyty, w pole godzina wstaw ustaloną godzinę pierwszej możliwej wizyty
  • dodaj rekord do tabeli lekarze_zdarzenia, zdarzenie_id = 5, opis = Pacjent: Nazwisko Imię pacjenta + data i godzina wizyty
  • dodaj rekord do tabeli pacjenci_zdarzenia, zdarzenie_id = 5, opis = Pacjent: Nazwisko Imię lekarza + data i godzina wizyty

Logika funkcji zaplanuj_wizyte - rozbudowa:

  • sprawdzaj, czy lekarz jest aktywny, jeśli nie to błąd
  • dodaj kontrolę, która pozwala na zaplanowanie pacjentowi tylko jednej wizyty w danym dniu

Szybka migracja zarządzania stanem do Pinia Store:

Strona /lekarze/:id - migracja do Pinia Store:

  • strona ma wiele elementów, które potrzebują danych z Supabase REST API
  • wszystkie dane potrzebne na tej stronie, łącznie z zagnieżdżonymi komponentami, mają korzystać z globalnego Pinia Store reprezentującego dane lekarza
  • wszystkie wywołania Supabase REST API mają znaleźć się w akcjach store
  • wykonanie akcji takich jak zakończenie wizyty, odwołanie wizyty, usunięcie dostępności, ustalenie dostępności powinny inicjować odświeżenie tabeli historii zdarzeń lekarza

Strona /pacjenci/:id - migracja do Pinia Store:

  • podobnie jak dla lekarzy, migruj do Pinia Store obsługę stanu i komunikacji z Supabase REST API
  • akcje takie jak zakończenie wizyty, odwołanie wizyty, powinny inicjować odświeżenie tabeli historii zdarzeń pacjenta

Przykłady promptów korygujących agenta, czyli strofowanie asystenta:

Sprawdź komponent UPagination, stronicowanie dalej nie działa.

Nie działa wywołanie okna dialogowego, nie użyto useOverlay.

Okno dialogowe otwiera się, ale nic się nie pokazuje. Sprawdź dokumentację UModal.

Lista rozwijana lekarzy rodzinnych nie pokazuje wartości do wybory.

Nie podoba mi się rozwiązanie z natywnym form. Użyj UForm, który umożliwia integracje ze schematem walidacji zod.

Materiał wideo

Nagranie demonstrujące co potrafi 2-dniowy prototyp.