Files
Portfolio/blog-posts/41-supabase-fullstack-platform.md
damjan_savicandClaude Opus 4.5 e1bbe5455d Initial commit: Portfolio Website
Vollständige Next.js 15 Portfolio-Website mit:
- Blog-System mit 100+ Artikeln
- Supabase-Integration
- Responsive Design mit Tailwind CSS
- TypeScript-Konfiguration
- Testing-Setup mit Vitest und Playwright

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 15:07:20 +01:00

586 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Supabase: Die Open-Source Firebase Alternative für 2026
**Meta-Description:** Supabase als vollständige Backend-Plattform. PostgreSQL, Realtime, Auth, Edge Functions und Vector Embeddings in einem Stack.
**Keywords:** Supabase, PostgreSQL, Realtime Database, Edge Functions, Authentication, Firebase Alternative, Vector Embeddings
---
## Einführung
Supabase ist die **Open-Source Firebase Alternative**, die auf PostgreSQL aufbaut. 2026 bietet es eine vollständige Backend-Plattform: Database, Auth, Realtime, Edge Functions, Storage und Vector Embeddings alles integriert.
---
## Supabase Stack
```
┌─────────────────────────────────────────────────────────────┐
│ SUPABASE PLATFORM │
├─────────────────────────────────────────────────────────────┤
│ │
│ Database Layer: │
│ ├── PostgreSQL (Enterprise-Grade) │
│ ├── Row Level Security (RLS) │
│ ├── PostgREST (Auto-Generated APIs) │
│ └── pgvector (Vector Embeddings) │
│ │
│ Realtime Layer: │
│ ├── Postgres Changes (CDC) │
│ ├── Broadcast (User-to-User) │
│ ├── Presence (Online Status) │
│ └── WebSocket Connections │
│ │
│ Auth Layer: │
│ ├── Email/Password │
│ ├── OAuth Providers (Google, GitHub, etc.) │
│ ├── Magic Links │
│ └── Phone/SMS Auth │
│ │
│ Edge Functions: │
│ ├── Deno Runtime │
│ ├── TypeScript/JavaScript │
│ └── Global Distribution │
│ │
│ Storage: │
│ ├── S3-Compatible │
│ ├── CDN Integration │
│ └── Image Transformations │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Database Setup
```typescript
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
import { Database } from './database.types';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey);
// Server-Side mit Service Role
import { createClient } from '@supabase/supabase-js';
export const supabaseAdmin = createClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{
auth: {
autoRefreshToken: false,
persistSession: false
}
}
);
```
### Schema Definition (SQL)
```sql
-- Users Tabelle (erweitert auth.users)
CREATE TABLE public.profiles (
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
username TEXT UNIQUE,
full_name TEXT,
avatar_url TEXT,
bio TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Posts Tabelle
CREATE TABLE public.posts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
author_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
title TEXT NOT NULL,
content TEXT,
published BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Trigger für updated_at
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER profiles_updated_at
BEFORE UPDATE ON public.profiles
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
CREATE TRIGGER posts_updated_at
BEFORE UPDATE ON public.posts
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
```
---
## Row Level Security (RLS)
```sql
-- RLS aktivieren
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
-- Profiles: Jeder kann lesen, nur Owner kann schreiben
CREATE POLICY "Profiles sind öffentlich lesbar"
ON public.profiles FOR SELECT
USING (true);
CREATE POLICY "User kann eigenes Profil bearbeiten"
ON public.profiles FOR UPDATE
USING (auth.uid() = id);
-- Posts: Veröffentlichte sind lesbar, Autor kann alles
CREATE POLICY "Veröffentlichte Posts sind lesbar"
ON public.posts FOR SELECT
USING (published = true OR auth.uid() = author_id);
CREATE POLICY "Autor kann eigene Posts erstellen"
ON public.posts FOR INSERT
WITH CHECK (auth.uid() = author_id);
CREATE POLICY "Autor kann eigene Posts bearbeiten"
ON public.posts FOR UPDATE
USING (auth.uid() = author_id);
CREATE POLICY "Autor kann eigene Posts löschen"
ON public.posts FOR DELETE
USING (auth.uid() = author_id);
```
---
## CRUD Operations
```typescript
// CREATE
async function createPost(post: { title: string; content: string }) {
const { data, error } = await supabase
.from('posts')
.insert({
title: post.title,
content: post.content,
author_id: (await supabase.auth.getUser()).data.user?.id
})
.select()
.single();
if (error) throw error;
return data;
}
// READ
async function getPosts() {
const { data, error } = await supabase
.from('posts')
.select(`
*,
author:profiles(username, avatar_url)
`)
.eq('published', true)
.order('created_at', { ascending: false })
.limit(10);
if (error) throw error;
return data;
}
// READ mit Pagination
async function getPostsPaginated(page: number, pageSize: number = 10) {
const from = page * pageSize;
const to = from + pageSize - 1;
const { data, error, count } = await supabase
.from('posts')
.select('*, author:profiles(username)', { count: 'exact' })
.eq('published', true)
.range(from, to);
return { data, count, hasMore: count ? to < count - 1 : false };
}
// UPDATE
async function updatePost(id: string, updates: Partial<Post>) {
const { data, error } = await supabase
.from('posts')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) throw error;
return data;
}
// DELETE
async function deletePost(id: string) {
const { error } = await supabase
.from('posts')
.delete()
.eq('id', id);
if (error) throw error;
}
```
---
## Realtime Subscriptions
```typescript
import { useEffect, useState } from 'react';
import { supabase } from '@/lib/supabase';
import { RealtimeChannel } from '@supabase/supabase-js';
// 1. Database Changes (CDC)
function useRealtimePosts() {
const [posts, setPosts] = useState<Post[]>([]);
useEffect(() => {
// Initial Load
supabase.from('posts').select('*').then(({ data }) => {
if (data) setPosts(data);
});
// Subscribe to Changes
const channel = supabase
.channel('posts-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'posts'
},
(payload) => {
if (payload.eventType === 'INSERT') {
setPosts(prev => [payload.new as Post, ...prev]);
} else if (payload.eventType === 'UPDATE') {
setPosts(prev =>
prev.map(p => p.id === payload.new.id ? payload.new as Post : p)
);
} else if (payload.eventType === 'DELETE') {
setPosts(prev => prev.filter(p => p.id !== payload.old.id));
}
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, []);
return posts;
}
// 2. Broadcast (User-to-User Messaging)
function useBroadcast(roomId: string) {
const [messages, setMessages] = useState<Message[]>([]);
useEffect(() => {
const channel = supabase.channel(`room:${roomId}`)
.on('broadcast', { event: 'message' }, ({ payload }) => {
setMessages(prev => [...prev, payload]);
})
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [roomId]);
const sendMessage = (content: string) => {
supabase.channel(`room:${roomId}`).send({
type: 'broadcast',
event: 'message',
payload: { content, timestamp: new Date().toISOString() }
});
};
return { messages, sendMessage };
}
// 3. Presence (Online Status)
function usePresence(roomId: string, userId: string) {
const [onlineUsers, setOnlineUsers] = useState<string[]>([]);
useEffect(() => {
const channel = supabase.channel(`presence:${roomId}`)
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState();
const users = Object.values(state).flat().map(p => p.user_id);
setOnlineUsers(users);
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({ user_id: userId });
}
});
return () => {
supabase.removeChannel(channel);
};
}, [roomId, userId]);
return onlineUsers;
}
```
---
## Authentication
```typescript
// Sign Up
async function signUp(email: string, password: string) {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`
}
});
return { data, error };
}
// Sign In
async function signIn(email: string, password: string) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
});
return { data, error };
}
// OAuth Sign In
async function signInWithOAuth(provider: 'google' | 'github') {
const { data, error } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${window.location.origin}/auth/callback`
}
});
return { data, error };
}
// Sign Out
async function signOut() {
const { error } = await supabase.auth.signOut();
return { error };
}
// Auth State Hook
function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
setLoading(false);
});
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null);
}
);
return () => subscription.unsubscribe();
}, []);
return { user, loading };
}
```
---
## Edge Functions
```typescript
// supabase/functions/send-email/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type'
};
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}
try {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);
const { to, subject, body } = await req.json();
// Send email via Resend, SendGrid, etc.
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('RESEND_API_KEY')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: 'noreply@example.com',
to,
subject,
html: body
})
});
const result = await response.json();
return new Response(JSON.stringify(result), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
}
});
// Client-Side Aufruf
const { data, error } = await supabase.functions.invoke('send-email', {
body: {
to: 'user@example.com',
subject: 'Welcome!',
body: '<h1>Welcome to our platform!</h1>'
}
});
```
---
## Vector Embeddings (AI)
```sql
-- pgvector Extension aktivieren
CREATE EXTENSION IF NOT EXISTS vector;
-- Documents Tabelle mit Embeddings
CREATE TABLE documents (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
content TEXT NOT NULL,
embedding VECTOR(1536), -- OpenAI ada-002 Dimension
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Index für schnelle Similarity Search
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- Similarity Search Funktion
CREATE OR REPLACE FUNCTION search_documents(
query_embedding VECTOR(1536),
match_count INT DEFAULT 5
)
RETURNS TABLE (
id UUID,
content TEXT,
similarity FLOAT
) AS $$
BEGIN
RETURN QUERY
SELECT
documents.id,
documents.content,
1 - (documents.embedding <=> query_embedding) AS similarity
FROM documents
ORDER BY documents.embedding <=> query_embedding
LIMIT match_count;
END;
$$ LANGUAGE plpgsql;
```
```typescript
// Embedding generieren und speichern
async function addDocument(content: string) {
// Embedding via OpenAI
const embeddingResponse = await openai.embeddings.create({
model: 'text-embedding-ada-002',
input: content
});
const embedding = embeddingResponse.data[0].embedding;
// In Supabase speichern
const { data, error } = await supabase
.from('documents')
.insert({ content, embedding })
.select()
.single();
return data;
}
// Similarity Search
async function searchDocuments(query: string) {
// Query Embedding
const embeddingResponse = await openai.embeddings.create({
model: 'text-embedding-ada-002',
input: query
});
const queryEmbedding = embeddingResponse.data[0].embedding;
// Search via RPC
const { data, error } = await supabase.rpc('search_documents', {
query_embedding: queryEmbedding,
match_count: 5
});
return data;
}
```
---
## Fazit
Supabase bietet 2026:
1. **PostgreSQL Power**: Enterprise-DB mit RLS, Triggers, Functions
2. **Realtime Built-in**: CDC, Broadcast, Presence über WebSockets
3. **Edge Functions**: Deno-basiert, global verteilt
4. **Vector Search**: pgvector für AI/RAG-Anwendungen
Eine vollständige Backend-Plattform für moderne Anwendungen.
---
## Bildprompts
1. "Database and real-time connections flowing together, Supabase architecture visualization"
2. "PostgreSQL elephant with real-time lightning bolts, modern database concept"
3. "Edge functions distributed globally on world map, serverless infrastructure"
---
## Quellen
- [Supabase Documentation](https://supabase.com/docs)
- [Supabase Features](https://supabase.com/features)
- [Supabase GitHub](https://github.com/supabase/supabase)
- [Supabase Review 2026](https://hackceleration.com/supabase-review/)