Supabase od zera — baza danych, auth i storage w jednym

Supabase to stack backendowy, który pozwala budować aplikacje produkcyjne bez zarządzania infrastrukturą. PostgreSQL pod spodem, REST i GraphQL API generowane automatycznie, auth out-of-the-box, storage dla plików, Realtime przez WebSockety. Wszystko open-source, możliwe do self-hostowania.

Porównanie z Firebase: Supabase to PostgreSQL z relacyjnym modelem danych — nie NoSQL. Możesz pisać JOIN, korzystasz z transakcji, masz pełny SQL. To znacząca różnica jeśli Twoje dane mają relacje (czyli prawie zawsze).

Ten artykuł zakłada znajomość TypeScript i podstaw SQL. Przejdziemy przez pełny setup — od `supabase init` do produkcyjnego checklist.

Local dev setup

Supabase CLI to twój przyjaciel przy lokalnym developmencie:

```bash npm install supabase --save-dev npx supabase init npx supabase start ```

`supabase start` odpala lokalne kontenery Docker: PostgreSQL, Auth, Storage, Studio (panel lokalny dostępny na localhost:54323). Dostajesz też lokalne URL i klucze anon/service_role w terminalu.

Zatrzymanie środowiska:

```bash npx supabase stop ```

Lokalny setup izoluje środowiska developerskie — każdy developer ma własną instancję bazy. Dobry nawyk: nie pisz bezpośrednio do zdalnej bazy w trakcie developmentu.

Tworzenie tabel: Dashboard, SQL editor, migracje

Masz trzy opcje:

**Dashboard** (supabase.com → Table Editor) — wizualny kreator tabel. Dobry dla prototypowania, ale nie nadaje się do pracy zespołowej — zmiany nie są śledzone w repozytorium.

**SQL Editor** — piszesz SQL bezpośrednio w panelu. Szybkie, ale tak samo nie wersjonowane.

**Migracje** — właściwe podejście dla produkcji:

```bash npx supabase migration new create_profiles_table ```

Tworzy plik w `supabase/migrations/`. Wypełnij go SQL:

```sql create table public.profiles ( id uuid references auth.users(id) on delete cascade primary key, username text unique not null, full_name text, avatar_url text, created_at timestamptz default now() );

-- Włącz RLS od razu alter table public.profiles enable row level security; ```

Aplikuj lokalnie:

```bash npx supabase db reset ```

Wypchnij do zdalnej bazy:

```bash npx supabase db push ```

Migracje są w repozytorium — cały zespół ma ten sam schemat bazy. Code review dla zmian schematu jest możliwy.

TypeScript client z generowanymi typami

Generowanie typów na podstawie schematu bazy:

```bash npx supabase gen types typescript --local > src/types/database.ts ```

Plik `database.ts` zawiera typy dla wszystkich tabel i widoków. Używasz go przy inicjalizacji klienta:

```typescript import { createClient } from '@supabase/supabase-js'; import type { Database } from './types/database';

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;

export const supabase = createClient(supabaseUrl, supabaseAnonKey); ```

Teraz zapytania są w pełni otypowane:

```typescript // TypeScript wie, że profiles ma pole username, full_name etc. const { data: profiles, error } = await supabase .from('profiles') .select('id, username, full_name') .eq('username', 'jan_kowalski') .single();

// profiles jest typu Database['public']['Tables']['profiles']['Row'] | null ```

Regeneruj typy po każdej migracji. Warto dodać skrypt do `package.json`:

```json { "scripts": { "db:types": "supabase gen types typescript --local > src/types/database.ts" } } ```

Row Level Security: polityki dostępu

RLS (Row Level Security) to mechanizm PostgreSQL, który kontroluje które wiersze użytkownik może odczytać lub modyfikować. Supabase egzekwuje RLS przez JWT — anon key i user session automatycznie trafiają do kontekstu PostgreSQL.

Włączenie RLS na tabeli (domyślnie blokuje wszystko):

```sql alter table public.profiles enable row level security; ```

Polityki przykładowe:

```sql -- Użytkownik może czytać tylko swój profil create policy "Users can read own profile" on public.profiles for select using (auth.uid() = id);

-- Użytkownik może edytować tylko swój profil create policy "Users can update own profile" on public.profiles for update using (auth.uid() = id);

-- Każdy może czytać publiczne profile (przykład otwartej polityki) create policy "Public profiles are readable" on public.profiles for select using (true); ```

`auth.uid()` to funkcja Supabase, która zwraca UUID zalogowanego użytkownika z JWT. Polityki są ewaluowane po stronie bazy danych — nawet jeśli ktoś spreparuje zapytanie, PostgreSQL wymusi limit.

Ważne: tabele bez polityk przy włączonym RLS blokują **wszystkie** operacje. Jeśli zapytanie zwraca pustą tablicę zamiast błędu — sprawdź najpierw czy masz polityki.

Auth: email, magic link, OAuth

**Email i hasło:**

```typescript // Rejestracja const { data, error } = await supabase.auth.signUp({ email: 'jan@example.com', password: 'haslo123', });

// Logowanie const { data, error } = await supabase.auth.signInWithPassword({ email: 'jan@example.com', password: 'haslo123', });

// Wylogowanie await supabase.auth.signOut(); ```

**Magic link (bez hasła):**

```typescript const { error } = await supabase.auth.signInWithOtp({ email: 'jan@example.com', options: { emailRedirectTo: 'https://twoja-aplikacja.pl/auth/callback', }, }); ```

**OAuth z Google:**

W panelu Supabase: Authentication → Providers → Google. Wklej Client ID i Client Secret z Google Cloud Console.

```typescript const { error } = await supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: 'https://twoja-aplikacja.pl/auth/callback', }, }); ```

Pobieranie aktualnej sesji:

```typescript const { data: { session } } = await supabase.auth.getSession(); const user = session?.user;

// Nasłuchiwanie zmian stanu auth supabase.auth.onAuthStateChange((event, session) => { if (event === 'SIGNED_IN') { console.log('Zalogowano:', session?.user.email); } if (event === 'SIGNED_OUT') { console.log('Wylogowano'); } }); ```

Storage: buckety, upload, publiczne URL

Storage w Supabase działa jak S3-compatible object store z integracją z Auth i RLS.

Tworzenie bucketu przez klienta (lub przez Dashboard):

```typescript const { data, error } = await supabase.storage.createBucket('avatars', { public: false, // prywatny bucket — dostęp przez polityki fileSizeLimit: 1024 * 1024 * 2, // 2MB allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'], }); ```

Upload pliku:

```typescript const file = event.target.files[0]; // np. z const filePath = `${user.id}/avatar.${file.name.split('.').pop()}`;

const { data, error } = await supabase.storage .from('avatars') .upload(filePath, file, { upsert: true, // nadpisz jeśli plik istnieje }); ```

Publiczny URL (dla publicznych bucketów):

```typescript const { data } = supabase.storage .from('avatars') .getPublicUrl(filePath);

console.log(data.publicUrl); ```

Podpisany URL (dla prywatnych bucketów, ważny przez określony czas):

```typescript const { data, error } = await supabase.storage .from('avatars') .createSignedUrl(filePath, 3600); // ważny 1h ```

Realtime: subscribowanie zmian w tabeli

Supabase Realtime pozwala nasłuchiwać na zmiany w bazie przez WebSockety. Opiera się na PostgreSQL logical replication.

```typescript const channel = supabase .channel('messages-changes') .on( 'postgres_changes', { event: '*', // INSERT | UPDATE | DELETE | * schema: 'public', table: 'messages', filter: 'room_id=eq.123', // opcjonalny filtr }, (payload) => { console.log('Zmiana w tabeli:', payload.eventType); console.log('Nowy rekord:', payload.new); console.log('Stary rekord:', payload.old); } ) .subscribe();

// Odsubskrybuj gdy komponent unmountuje // channel.unsubscribe(); ```

Ważne: tabela musi mieć włączone Realtime w Dashboard (Table Editor → tabela → Realtime) lub przez SQL:

```sql alter publication supabase_realtime add table public.messages; ```

Edge Functions: kiedy używać

Edge Functions to Deno-based serverless funkcje deployowane globalnie blisko użytkownika. Kiedy ich używać:

- Integracje z zewnętrznymi API (webhooks od Stripe, SendGrid, etc.) - Logika, która wymaga service_role key (nie chcesz eksponować go na froncie) - Transformacje danych przed zapisem do bazy - Harmonogramy (cron jobs przez `pg_cron` lub zewnętrzny trigger)

Kiedy **nie** używać Edge Functions: do wszystkiego co można zrobić przez RLS i bezpośrednie zapytania klienta. Edge Functions dodają latencję cold start.

Przykład prostej funkcji:

```typescript // supabase/functions/send-welcome-email/index.ts import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';

serve(async (req) => { const { email } = await req.json();

// Wywołanie zewnętrznego API emailowego await fetch('https://api.sendgrid.com/v3/mail/send', { method: 'POST', headers: { Authorization: `Bearer ${Deno.env.get('SENDGRID_API_KEY')}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ to: [{ email }], from: { email: 'noreply@twoja-aplikacja.pl' }, subject: 'Witaj!', text: 'Dziękujemy za rejestrację.', }), });

return new Response(JSON.stringify({ ok: true }), { headers: { 'Content-Type': 'application/json' }, }); }); ```

Checklist produkcyjny

Przed wdrożeniem na produkcję:

**Baza danych:** - Wszystkie tabele mają włączone RLS i zdefiniowane polityki - Indeksy na kolumnach używanych w WHERE i JOIN (`create index on profiles(username);`) - Connection pooling przez pgBouncer włączony (Supabase oferuje to out-of-the-box w trybie Transaction) - Backupy skonfigurowane (daily backups dostępne na płatnych planach)

**Auth:** - Email confirmation włączony dla nowych kont - Redirect URLs ograniczone tylko do Twoich domen - JWT expiry ustawiony świadomie (domyślnie 1h access token, 7 dni refresh)

**Security:** - `service_role` key wyłącznie po stronie serwera (nigdy na froncie) - `anon` key może być publiczny, ale RLS musi go chronić - Row-level security przetestowane dla każdej tabeli - Polityki storage sprawdzone dla każdego bucketu

**Monitoring:** - Logi zapytań włączone w Dashboard → Logs - Alerty na błędy 5xx

Supabase redukuje czas do działającego backendu z tygodni do godzin. PostgreSQL pod spodem oznacza, że nie masz ograniczeń NoSQL — relacje, transakcje, pełny SQL. RLS to jeden z najmocniejszych mechanizmów bezpieczeństwa dostępnych bez pisania własnego middleware. Naucz się go dobrze — to core Supabase.

Supabase PostgreSQL baza danych auth storage SaaS TypeScript
Udostępnij:

Potrzebujesz podobnego rozwiązania?

Porozmawiajmy o Twoim projekcie

Pierwsza rozmowa jest bezpłatna. Opisz nam swój pomysł — odpowiemy w ciągu jednego dnia roboczego.

Umów bezpłatną rozmowę
Wróć do wszystkich artykułów