Building with Supabase
Purpose
Build secure, scalable applications using Supabase's PostgreSQL platform:
-
Design tables with proper Row Level Security (RLS)
-
Implement authentication flows (email, OAuth, magic link)
-
Create real-time subscriptions for live updates
-
Build Edge Functions for serverless logic
-
Manage file storage with security policies
Quick Start
// Initialize Supabase client import { createClient } from '@supabase/supabase-js'; import { Database } from './types/supabase';
export const supabase = createClient<Database>( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! );
// Server-side with service role (bypasses RLS) import { createClient } from '@supabase/supabase-js'; export const supabaseAdmin = createClient<Database>( process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY! );
Features
Feature Description Guide
PostgreSQL Full Postgres with extensions (pgvector, PostGIS) Direct SQL or Supabase client
Row Level Security Per-row access control policies Enable RLS + create policies
Authentication Email, OAuth, magic link, phone OTP Built-in auth.users table
Real-time Live database change subscriptions Channel subscriptions
Edge Functions Deno serverless functions TypeScript at edge
Storage S3-compatible file storage Buckets with RLS policies
Common Patterns
RLS Policy Patterns
-- Enable RLS on table ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Owner-based access CREATE POLICY "Users can CRUD own posts" ON posts FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
-- Public read, authenticated write CREATE POLICY "Anyone can read posts" ON posts FOR SELECT USING (published = true);
CREATE POLICY "Authenticated users can create" ON posts FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
-- Team-based access CREATE POLICY "Team members can access" ON documents FOR ALL USING ( team_id IN ( SELECT team_id FROM team_members WHERE user_id = auth.uid() ) );
-- Role-based access using JWT claims CREATE POLICY "Admins can do anything" ON users FOR ALL USING (auth.jwt() ->> 'role' = 'admin');
Authentication Flow
// Sign up with email const { data, error } = await supabase.auth.signUp({ email: 'user@example.com', password: 'secure-password', options: { data: { full_name: 'John Doe' }, // Custom user metadata emailRedirectTo: 'https://app.com/auth/callback', }, });
// OAuth sign in const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: 'https://app.com/auth/callback', scopes: 'email profile', }, });
// Magic link const { error } = await supabase.auth.signInWithOtp({ email: 'user@example.com', options: { emailRedirectTo: 'https://app.com/auth/callback' }, });
// Get current user const { data: { user } } = await supabase.auth.getUser();
// Sign out await supabase.auth.signOut();
Real-time Subscriptions
// Subscribe to table changes const channel = supabase .channel('posts-changes') .on( 'postgres_changes', { event: '*', // INSERT, UPDATE, DELETE, or * schema: 'public', table: 'posts', filter: 'user_id=eq.' + userId, // Optional filter }, (payload) => { console.log('Change:', payload.eventType, payload.new); } ) .subscribe();
// Cleanup channel.unsubscribe();
Edge Functions
// supabase/functions/process-webhook/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 supabase = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! );
const { record } = await req.json();
// Process webhook... await supabase.from('processed').insert({ data: record });
return new Response(JSON.stringify({ success: true }), { headers: { 'Content-Type': 'application/json' }, }); });
Storage with Policies
-- Create bucket INSERT INTO storage.buckets (id, name, public) VALUES ('avatars', 'avatars', true);
-- Storage policies CREATE POLICY "Users can upload own avatar" ON storage.objects FOR INSERT WITH CHECK ( bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1] );
CREATE POLICY "Anyone can view avatars" ON storage.objects FOR SELECT USING (bucket_id = 'avatars');
// Upload file
const { data, error } = await supabase.storage
.from('avatars')
.upload(${userId}/avatar.png, file, {
cacheControl: '3600',
upsert: true,
});
// Get public URL
const { data: { publicUrl } } = supabase.storage
.from('avatars')
.getPublicUrl(${userId}/avatar.png);
Next.js Server Components
// app/api/posts/route.ts import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'; import { cookies } from 'next/headers';
export async function GET() { const supabase = createRouteHandlerClient({ cookies }); const { data: posts } = await supabase.from('posts').select('*'); return Response.json(posts); }
// Server Component import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'; import { cookies } from 'next/headers';
export default async function Page() { const supabase = createServerComponentClient({ cookies }); const { data: posts } = await supabase.from('posts').select('*'); return <PostList posts={posts} />; }
Use Cases
-
Building SaaS applications with multi-tenant RLS
-
Real-time collaborative applications
-
Mobile app backends with authentication
-
Serverless APIs with Edge Functions
-
File upload systems with access control
Best Practices
Do Avoid
Enable RLS on all tables Disabling RLS "temporarily" in production
Use auth.uid() in policies, not session data Trusting client-side user ID
Create service role client only server-side Exposing service role key to client
Use TypeScript types from supabase gen types
Manual type definitions
Filter subscriptions to reduce bandwidth Subscribing to entire tables
Use supabase db push for dev, migrations for prod Pushing directly to production
Set up proper bucket policies Public buckets for sensitive files
Use signInWithOAuth for social auth Custom OAuth implementations
CLI Commands
Local development
supabase start # Start local Supabase supabase db reset # Reset with migrations + seed
Migrations
supabase migration new add_posts # Create migration supabase db push # Push to linked project (dev only) supabase db diff --use-migra # Generate migration from diff
Type generation
supabase gen types typescript --local > types/supabase.ts
Edge Functions
supabase functions serve # Local development supabase functions deploy my-func # Deploy to production
Related Skills
See also these related skill documents:
-
designing-database-schemas - Schema design patterns
-
managing-database-migrations - Migration strategies
-
implementing-oauth - OAuth flow details