Supabase: Das All-in-One Backend für KI-Applikationen
Wer heute eine KI-Applikation baut, braucht typischerweise: eine relationale Datenbank für strukturierte Daten, eine Vektordatenbank für Embeddings, ein Auth-System für Benutzer und Object Storage für Dateien. In der Praxis bedeutet das vier verschiedene Services, vier verschiedene SDKs, vier verschiedene Billing-Konten.
Supabase löst genau dieses Problem. Auf Basis von PostgreSQL — der zuverlässigsten Open-Source-Datenbank der Welt — bündelt Supabase alles in einem Stack. Das pgvector-Extension macht die PostgreSQL-Instanz zur Vektordatenbank, GoTrue übernimmt Authentication, und ein S3-kompatibles Storage-Layer rundet das Paket ab.
AuthGoTrue, JWT, OAuth Providers
DatabasePostgreSQL 15+, Row Level Security
Vectorpgvector, ivfflat, hnsw Index
StorageS3-kompatibel, CDN, Transforms
RealtimeWebSocket, Broadcast, Presence
Edge FunctionsDeno Runtime, weltweit verteilt
Für Claude Code ideal: Supabase hat eine hervorragende TypeScript-Typisierung. Claude Code generiert typsichere Datenbankoperationen, die exakt zur Datenbankstruktur passen — ohne Laufzeitfehler durch falsche Spaltennamen.
Claude Code Setup mit Supabase: Client, RLS und Migrations
Der erste Schritt ist immer die saubere Einrichtung des Supabase-Clients und der Row-Level-Security-Policies. Claude Code kennt die Supabase-Konventionen und generiert Migrations-Dateien, die direkt mit supabase db push eingespielt werden können.
Supabase Client initialisieren + Typen generieren
# Prompt: "Richte Supabase in einem Next.js-Projekt ein:
# - Server-Side und Client-Side Client
# - Typen aus der Datenbank generieren
# - Environment-Variables validieren beim Start"
## Terminal: Typen generieren
npx supabase gen types typescript \
--project-id $SUPABASE_PROJECT_ID \
--schema public > src/types/supabase.ts
## src/lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import type { Database } from '@/types/supabase'
export function createSupabaseServerClient() {
const cookieStore = cookies()
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name) { return cookieStore.get(name)?.value },
set(name, value, options) { cookieStore.set({ name, value, ...options }) },
remove(name, options) { cookieStore.set({ name, value: '', ...options }) }
}
}
)
}
## src/lib/supabase/admin.ts — nur serverseitig!
import { createClient } from '@supabase/supabase-js'
export const supabaseAdmin = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY! // NIEMALS im Client-Bundle!
)
RLS-Policies und Migrations mit Claude Code
# Prompt: "Erstelle eine Migration für eine documents-Tabelle
# mit RLS: User kann nur eigene Dokumente lesen und schreiben."
-- supabase/migrations/20260505_documents.sql
CREATE TABLE documents (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Row Level Security aktivieren
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
-- Policy: nur eigene Zeilen lesen
CREATE POLICY "users_select_own" ON documents
FOR SELECT USING (auth.uid() = user_id);
-- Policy: nur eigene Zeilen einfügen
CREATE POLICY "users_insert_own" ON documents
FOR INSERT WITH CHECK (auth.uid() = user_id);
-- Policy: nur eigene Zeilen aktualisieren
CREATE POLICY "users_update_own" ON documents
FOR UPDATE USING (auth.uid() = user_id);
-- Policy: nur eigene Zeilen löschen
CREATE POLICY "users_delete_own" ON documents
FOR DELETE USING (auth.uid() = user_id);
Wichtig: Service Role Key schützen! Der SUPABASE_SERVICE_ROLE_KEY umgeht alle RLS-Policies. Er darf ausschließlich in Server-Side-Code verwendet werden — niemals im Browser-Bundle oder in Next.js Client Components.
Vector Search mit pgvector: Embeddings speichern und Similarity Search
pgvector ist eine PostgreSQL-Extension, die Vektor-Operationen direkt in der Datenbank ermöglicht. Statt eine separate Vektordatenbank wie Pinecone oder Weaviate zu betreiben, laufen Embedding-Storage und Similarity Search auf derselben PostgreSQL-Instanz — mit allen Vorteilen von ACID-Transaktionen und RLS.
pgvector aktivieren und Embeddings speichern
# Prompt: "Aktiviere pgvector und erstelle eine Tabelle für
# Text-Embeddings (1536 Dimensionen für OpenAI text-embedding-3-small).
# Erstelle einen ivfflat-Index für schnelle Similarity Search."
-- Migration: pgvector aktivieren
CREATE EXTENSION IF NOT EXISTS vector;
-- Embeddings-Tabelle
CREATE TABLE document_chunks (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
document_id UUID REFERENCES documents(id) ON DELETE CASCADE,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
chunk_index INTEGER NOT NULL,
content TEXT NOT NULL,
embedding vector(1536), -- OpenAI text-embedding-3-small
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now()
);
-- ivfflat-Index (schnell bei >10k Vektoren)
CREATE INDEX ON document_chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- RLS wie bei der documents-Tabelle
ALTER TABLE document_chunks ENABLE ROW LEVEL SECURITY;
CREATE POLICY "chunks_select_own" ON document_chunks
FOR SELECT USING (auth.uid() = user_id);
Similarity Search als PostgreSQL-Funktion
-- Prompt: "Erstelle eine match_chunks-Funktion die:
-- - einen Query-Embedding-Vektor nimmt
-- - die k ähnlichsten Chunks zurückgibt
-- - nach Cosine Similarity sortiert (0.0 bis 1.0)"
CREATE OR REPLACE FUNCTION match_chunks (
query_embedding vector(1536),
match_count INT DEFAULT 5,
similarity_threshold FLOAT DEFAULT 0.7
)
RETURNS TABLE (
id UUID,
content TEXT,
similarity FLOAT,
metadata JSONB
)
LANGUAGE SQL STABLE AS $$
SELECT
id,
content,
1 - (embedding <=> query_embedding) AS similarity,
metadata
FROM document_chunks
WHERE
user_id = auth.uid()
AND 1 - (embedding <=> query_embedding) > similarity_threshold
ORDER BY embedding <=> query_embedding
LIMIT match_count;
$$;
ivfflat vs. hnsw: Für weniger als 100.000 Vektoren ist ivfflat mit lists = sqrt(Anzahl_Vektoren) optimal. Bei mehr als 500.000 Vektoren lohnt sich der hnsw-Index — er ist langsamer beim Einfügen, aber deutlich schneller bei der Suche.
RAG-System mit Supabase bauen: Vom Dokument zur Antwort
Retrieval-Augmented Generation (RAG) ist das Herzstück moderner KI-Apps: Dokumente werden in Chunks zerlegt, als Embeddings gespeichert, und beim Query werden die semantisch ähnlichsten Chunks als Kontext an das Sprachmodell übergeben. Supabase ist dafür ideal — Storage, Embedding-Storage und Retrieval auf einer Plattform.
Schritt 1: Dokumente chunken und embedden
# Prompt: "Implementiere eine ingestDocument-Funktion die:
# 1. Dokument in 512-Zeichen-Chunks aufteilt (100 Zeichen Overlap)
# 2. Jeden Chunk mit OpenAI embeddet
# 3. Chunks mit Embeddings in Supabase speichert"
import { OpenAI } from 'openai'
import { createSupabaseServerClient } from '@/lib/supabase/server'
const openai = new OpenAI()
const CHUNK_SIZE = 512
const CHUNK_OVERLAP = 100
function chunkText(text: string): string[] {
const chunks: string[] = []
let start = 0
while (start < text.length) {
const end = Math.min(start + CHUNK_SIZE, text.length)
chunks.push(text.slice(start, end))
start += CHUNK_SIZE - CHUNK_OVERLAP
}
return chunks
}
export async function ingestDocument(documentId: string, content: string) {
const supabase = createSupabaseServerClient()
const chunks = chunkText(content)
// Embeddings in Batches von 100 generieren (Rate-Limit beachten)
const embeddings = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: chunks,
dimensions: 1536
})
// Alle Chunks in einer Transaktion speichern
const chunkRows = chunks.map((chunk, i) => ({
document_id: documentId,
chunk_index: i,
content: chunk,
embedding: embeddings.data[i].embedding,
metadata: { char_start: i * (CHUNK_SIZE - CHUNK_OVERLAP) }
}))
const { error } = await supabase
.from('document_chunks')
.insert(chunkRows)
if (error) throw new Error(`Ingest failed: ${error.message}`)
return { chunksCreated: chunks.length }
}
Schritt 2: Retrieval und Antwort generieren
# Prompt: "Implementiere die RAG-Query-Funktion:
# 1. Query embedden
# 2. Ähnlichste Chunks aus Supabase abrufen (match_chunks RPC)
# 3. Als Kontext an Claude übergeben, Antwort streamen"
import Anthropic from '@anthropic-ai/sdk'
const anthropic = new Anthropic()
export async function ragQuery(query: string) {
const supabase = createSupabaseServerClient()
// 1. Query embedden
const { data: [{ embedding }] } = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: query,
dimensions: 1536
})
// 2. Ähnlichste Chunks abrufen (via match_chunks RPC)
const { data: chunks, error } = await supabase
.rpc('match_chunks', {
query_embedding: embedding,
match_count: 5,
similarity_threshold: 0.7
})
if (error) throw error
// 3. Kontext aufbauen
const context = chunks
.map((c, i) => `[Quelle ${i+1}] ${c.content}`)
.join('\n\n')
// 4. Claude mit Kontext befragen (streaming)
const stream = await anthropic.messages.stream({
model: 'claude-opus-4-5',
max_tokens: 1024,
messages: [{
role: 'user',
content: `Beantworte die Frage ausschließlich basierend auf diesem Kontext:
${context}
Frage: ${query}`
}]
})
return stream // Stream direkt an Client weitergeben
}
RAG-Qualität verbessern: Claude Code hilft beim Prompt-Engineering für RAG. Nützlicher Zusatz: "Wenn die Antwort nicht im Kontext enthalten ist, sage das explizit — halluziniere keine Fakten." Dieser einfache Zusatz reduziert Halluzinationen drastisch.
Realtime Subscriptions: Live-Updates in KI-Apps
Supabase Realtime ermöglicht WebSocket-basierte Live-Updates direkt aus der PostgreSQL-Datenbank. Für KI-Apps ideal: wenn ein Hintergrundprozess Embeddings generiert, können Clients in Echtzeit über den Fortschritt informiert werden.
Realtime Subscription auf Dokument-Status
# Prompt: "Implementiere Realtime-Updates für den Ingestion-Status.
# Wenn status sich zu 'ready' ändert, soll die UI automatisch aktualisieren."
// src/hooks/useDocumentStatus.ts
import { useEffect, useState } from 'react'
import { createSupabaseBrowserClient } from '@/lib/supabase/client'
export function useDocumentStatus(documentId: string) {
const [status, setStatus] = useState<'processing' | 'ready' | 'error'>('processing')
const supabase = createSupabaseBrowserClient()
useEffect(() => {
// Aktuellen Status laden
supabase
.from('documents')
.select('status')
.eq('id', documentId)
.single()
.then(({ data }) => data && setStatus(data.status))
// Realtime-Subscription auf Status-Änderungen
const channel = supabase
.channel(`document-${documentId}`)
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'documents',
filter: `id=eq.${documentId}`
},
(payload) => setStatus(payload.new.status)
)
.subscribe()
// Cleanup beim Unmount
return () => { supabase.removeChannel(channel) }
}, [documentId])
return status
}
Realtime und RLS: Supabase Realtime respektiert Row-Level-Security. Clients empfangen nur Updates für Zeilen, auf die sie gemäß ihrer RLS-Policies Lesezugriff haben. Kein zusätzlicher Sicherheits-Layer nötig.
Edge Functions: Supabase Serverless + Claude API direkt
Supabase Edge Functions laufen in Deno auf Cloudflare Workers — weltweit verteilt mit kalten Startzeiten unter 50ms. Für KI-Apps perfekt: Embedding-Generierung und Claude-API-Calls können direkt in der Edge Function stattfinden, ohne eigenen Server.
Edge Function: Automatisches Embedden bei Dokument-Upload
# Prompt: "Erstelle eine Supabase Edge Function die beim INSERT
# in die documents-Tabelle (via Database Webhook) ausgelöst wird
# und das Dokument automatisch chunked und embeddet."
// supabase/functions/embed-document/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'
serve(async (req) => {
const { record } = await req.json() // Webhook-Payload
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Dokument-Content aus Storage holen (falls PDF/Datei)
const content = record.content ?? await fetchFromStorage(supabase, record.storage_path)
// Embeddings via OpenAI generieren
const openaiResponse = await fetch('https://api.openai.com/v1/embeddings', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ model: 'text-embedding-3-small', input: chunkText(content) })
})
const { data: embeddings } = await openaiResponse.json()
// Chunks in Supabase speichern
const chunks = chunkText(content).map((chunk, i) => ({
document_id: record.id,
user_id: record.user_id,
chunk_index: i,
content: chunk,
embedding: embeddings[i].embedding
}))
await supabase.from('document_chunks').insert(chunks)
// Status auf 'ready' setzen
await supabase.from('documents')
.update({ status: 'ready' })
.eq('id', record.id)
return new Response(JSON.stringify({ ok: true }))
})
Edge Function deployen und Webhook verbinden
# Deployment
npx supabase functions deploy embed-document --no-verify-jwt
# Secrets setzen
npx supabase secrets set OPENAI_API_KEY=sk-...
# Database Webhook in Supabase Dashboard:
# Table: documents | Event: INSERT
# URL: https://[project-id].supabase.co/functions/v1/embed-document
# HTTP Method: POST
# Lokale Entwicklung mit hot-reload:
npx supabase functions serve embed-document --env-file .env.local
Produktions-Checkliste: Claude Code + Supabase
- ✓
RLS auf allen Tabellen: Kein ALTER TABLE ... DISABLE ROW LEVEL SECURITY in Produktionsmigrations.
- ✓
Service Role Key sichern: Nur in Server-Side-Code, nie im Client-Bundle oder in Git ohne .gitignore.
- ✓
pgvector Index wählen: ivfflat für <100k Vektoren, hnsw für größere Datenmengen.
- ✓
Embedding-Dimensionen konsistent: Modell und Spaltendefinition müssen übereinstimmen — Mismatch gibt erst zur Laufzeit Fehler.
- ✓
Chunk-Overlap bei RAG: Mindestens 10% Overlap zwischen Chunks, sonst gehen Satz-Grenzen verloren.
- ✓
Realtime-Subscriptions aufräumen: supabase.removeChannel() im Cleanup-Handler — sonst Memory-Leaks.
- ✓
Edge Functions mit Tests: supabase functions serve + lokale Integration-Tests vor Deployment.
Supabase Free Tier Limits: 500 MB Datenbank, 1 GB Storage, 2 GB Bandbreite. Für ein produktives RAG-System mit vielen Dokumenten ist das Pro-Tier (€25/Monat) schnell nötig. pgvector benötigt zusätzlichen Arbeitsspeicher — das Free-Tier-Limit von 500 MB RAM kann bei größeren Index-Operationen ein Bottleneck werden.
Fazit: Supabase ist das ideale KI-Backend 2026
Supabase kombiniert alles, was eine moderne KI-Applikation braucht: PostgreSQL als zuverlässige Datenbasis, pgvector für Embeddings und Similarity Search, GoTrue für Authentication mit RLS-Integration, Realtime für Live-Updates, Edge Functions für serverlose KI-Workflows — und das alles hinter einem einzigen SDK.
Claude Code kennt die Supabase-Konventionen in und auswendig: korrekte RLS-Policies, typsichere Datenbankoperationen, optimale Index-Konfigurationen für pgvector. Was früher Tage dauerte — ein vollständiges Backend mit Vector Search aufzusetzen — ist mit Claude Code und Supabase eine Angelegenheit von Stunden.
Vollständiges Supabase + RAG-Modul im Kurs
Im Claude Code Mastery Kurs gibt es ein komplettes Supabase-Modul: RLS-Patterns, pgvector-Setup, RAG-System von Grund auf, Edge Functions und Realtime Subscriptions — mit produktionsreifem Code und Migrations.
14 Tage kostenlos testen →