Execute apply production-ready Supabase SDK patterns for TypeScript and Python. Use when implementing Supabase integrations, refactoring SDK usage, or establishing team coding standards for...
Production patterns for @supabase/supabase-js v2 and supabase-py, where every call returns { data, error } and success is never assumed. Covers client initialization, CRUD with filters, auth, realtime, storage, and RPC, with Python equivalents for the query patterns.
@supabase/supabase-js v2 installed (TypeScript) or supabase pip package (Python)supabase gen types typescriptCreate one client instance and reuse it. Never call createClient per-request โ a
singleton preserves the auth session and connection pool.
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
let supabase: ReturnType<typeof createClient<Database>>
export function getSupabase() {
if (!supabase) {
supabase = createClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{
auth: { autoRefreshToken: true, persistSession: true },
db: { schema: 'public' },
global: { headers: { 'x-app-name': 'my-app' } },
}
)
}
return supabase
}
Python equivalent:
from supabase import create_client, Client
_client: Client | None = None
def get_supabase() -> Client:
global _client
if _client is None:
_client = create_client(
os.environ["SUPABASE_URL"],
os.environ["SUPABASE_ANON_KEY"],
)
return _client
Destructure { data, error } and check error before touching data. Chain
filters onto .from(table).select(...); use .select().single() after an
insert/upsert to return the affected row. Skeleton:
const { data, error } = await getSupabase()
.from('users')
.select('id, name, email')
.eq('active', true)
.order('name')
.limit(10)
if (error) throw error
For the full CRUD set (insert-with-select, upsert on conflict, update, delete, RPC), the complete 12-filter reference table, and the Python equivalents, see queries and filters.
Auth, realtime channels, and storage all follow the same { data, error }
contract. Auth exposes signUp / signInWithPassword / getSession /
onAuthStateChange; realtime subscribes to postgres_changes on a channel and
requires removeChannel cleanup; storage does upload / download /
getPublicUrl / createSignedUrl per bucket. Full walkthroughs with code for
each: see auth, realtime, and storage.
Applying these patterns yields:
Database genericsEvery Supabase call returns { data, error }. Never skip the error check.
const { data, error } = await getSupabase().from('users').select('*')
if (error) {
// error is a PostgrestError with these fields:
// error.message โ human-readable description
// error.code โ Postgres error code (e.g., '23505')
// error.details โ additional context
// error.hint โ suggested fix from Postgres
console.error(`Query failed [${error.code}]: ${error.message}`)
throw error
}
// Only safe to use data after the error check
| Error Code | Meaning | What to Do |
|---|---|---|
PGRST116 |
No rows found (.single()) |
Return null or 404, don't throw |
23505 |
Unique-constraint violation (Postgres duplicate key) | Use .upsert() or show conflict error |
42501 |
RLS policy violation (Postgres insufficient privilege) | Check auth state and RLS policies |
PGRST000 |
Connection error | Retry with exponential backoff |
42P01 |
Table does not exist | Verify table name and run migrations |
23503 |
Foreign key violation | Ensure referenced row exists first |
42703 |
Column does not exist | Check column name, regenerate types |
The recommended production shape is a typed service layer that wraps the client so callers never touch raw queries, plus a pagination helper that returns a page of rows and the total count in one round trip. Both full, copy-ready implementations are in service patterns.
For database schema design, see supabase-schema-from-requirements. For auth deep-dive with RLS policies, see supabase-install-auth. For realtime architecture patterns, see supabase-auth-storage-realtime-core.