Skip to main content
โšก Calmops

Supabase Complete Guide: Open Source Firebase Alternative

Introduction

Supabase is an open-source Firebase alternative that provides a complete backend for modern applications. Built on PostgreSQL, it offers databases, authentication, real-time subscriptions, storage, and serverless functions. This comprehensive guide covers everything you need to build powerful applications with Supabase.

Understanding Supabase

What is Supabase?

Supabase is an “open source Firebase” that gives you a scalable backend without managing infrastructure. It combines PostgreSQL’s power with modern developer experience.

graph TB
    subgraph "Supabase Stack"
        DB[(PostgreSQL)]
        Auth[Authentication]
        Realtime[Real-time]
        Storage[Storage]
        Edge[Edge Functions]
        API[Auto API]
    end
    
    subgraph "Your App"
        Web[Web App]
        Mobile[Mobile App]
    end
    
    Web --> API
    Mobile --> API
    API --> DB
    API --> Auth
    API --> Realtime
    API --> Storage
    API --> Edge

Core Features

Feature Description
PostgreSQL Database Full-featured relational database
Authentication Email, OAuth, magic links, phone
Real-time Live subscriptions to database changes
Storage File storage with CDN
Edge Functions Serverless TypeScript functions
Auto API RESTful and GraphQL from your schema

Getting Started

Quick Setup

# Install Supabase CLI
npm install -g supabase

# Initialize new project
supabase init

# Start local development
supabase start

# Or create project at supabase.com

Connection String

// Supabase client
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = 'https://your-project.supabase.co'
const supabaseKey = 'your-anon-key'

const supabase = createClient(supabaseUrl, supabaseKey)

Database

Schema Design

-- Create tables
CREATE TABLE profiles (
  id UUID REFERENCES auth.users PRIMARY KEY,
  username TEXT UNIQUE,
  full_name TEXT,
  avatar_url TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE posts (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  user_id UUID REFERENCES profiles(id) NOT NULL,
  title TEXT NOT NULL,
  content TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE comments (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  post_id BIGINT REFERENCES posts(id) ON DELETE CASCADE NOT NULL,
  user_id UUID REFERENCES profiles(id) NOT NULL,
  content TEXT NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Enable Row Level Security
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE comments ENABLE ROW LEVEL SECURITY;

Row Level Security (RLS)

-- Profiles: Users can only update their own profile
CREATE POLICY "Users can update own profile"
  ON profiles
  FOR UPDATE
  USING (auth.uid() = id);

-- Profiles: Everyone can read
CREATE POLICY "Public profiles are viewable by everyone"
  ON profiles
  FOR SELECT
  USING (true);

-- Posts: Users can only see their own and public posts
CREATE POLICY "Users can view all posts"
  ON posts
  FOR SELECT
  USING (
    user_id = auth.uid() 
    OR EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND is_public = true)
  );

-- Posts: Users can insert their own
CREATE POLICY "Users can create posts"
  ON posts
  FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- Comments: Anyone can read
CREATE POLICY "Anyone can read comments"
  ON comments
  FOR SELECT
  USING (true);

-- Comments: Auth users can create
CREATE POLICY "Auth users can create comments"
  ON comments
  FOR INSERT
  WITH CHECK (auth.uid() = user_id);

Queries from Client

// Select data
const { data: profiles, error } = await supabase
  .from('profiles')
  .select('username, full_name')

// Filter
const { data: posts } = await supabase
  .from('posts')
  .select('*, profiles(username)')
  .eq('user_id', userId)
  .order('created_at', { ascending: false })
  .limit(10)

// Insert
const { data, error } = await supabase
  .from('posts')
  .insert([
    { 
      title: 'My Post', 
      content: 'Hello World',
      user_id: user.id 
    }
  ])

// Update
const { data, error } = await supabase
  .from('profiles')
  .update({ full_name: 'John Doe' })
  .eq('id', user.id)

// Delete
const { error } = await supabase
  .from('posts')
  .delete()
  .eq('id', postId)

PostgreSQL Functions

-- Create function to get post with author
CREATE OR REPLACE FUNCTION get_posts_with_authors()
RETURNS SETOF posts AS $$
BEGIN
  RETURN QUERY
  SELECT p.*
  FROM posts p
  ORDER BY p.created_at DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Call from client
const { data } = await supabase
  .rpc('get_posts_with_authors')

Authentication

Email/Password Auth

// Sign up
const { data, error } = await supabase.auth.signUp({
  email: '[email protected]',
  password: 'securepassword',
})

// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'securepassword',
})

// Sign out
await supabase.auth.signOut()

// Get current user
const { data: { user } } = await supabase.auth.getUser()

OAuth Providers

// GitHub OAuth
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    redirectTo: 'https://yourapp.com/auth/callback',
  }
})

// Google
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://yourapp.com/auth/callback',
  }
})

// Available providers: github, google, facebook, twitter, apple, etc.
// Send magic link
const { data, error } = await supabase.auth.signInWithOtp({
  email: '[email protected]',
})

Auth Helpers

// For React/Next.js
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'

const supabase = createClientComponentClient()

// Auth state change listener
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    console.log('Signed in:', session.user)
  }
})

Real-time

Subscribe to Changes

// Subscribe to table changes
const channel = supabase
  .channel('posts')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'posts'
    },
    (payload) => {
      console.log('New post:', payload.new)
    }
  )
  .subscribe()

// Subscribe to specific row
const channel = supabase
  .channel('posts')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'posts',
      filter: 'id=eq.1'
    },
    (payload) => {
      console.log('Post updated:', payload.new)
    }
  )
  .subscribe()

// Unsubscribe
channel.unsubscribe()

Broadcast (Pub/Sub)

// Simple pub/sub between clients
const channel = supabase
  .channel('chat-room')
  .on('broadcast', { event: 'typing' }, ({ payload }) => {
    console.log('User typing:', payload)
  })
  .subscribe()

// Send to channel
channel.send({
  type: 'broadcast',
  event: 'typing',
  payload: { user: 'John' }
})

Storage

Bucket Management

// Create bucket
const { data, error } = await supabase.storage
  .createBucket('avatars', {
    public: true,
    allowedMimeTypes: ['image/*'],
    fileSizeLimit: '2MB',
  })

// List buckets
const { data: buckets } = await supabase.storage.listBuckets()

File Operations

// Upload file
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('folder/avatar.png', file)

// Upload with custom name
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('avatar.png', file, {
    cacheControl: '3600',
    upsert: true,
  })

// Get public URL
const { data: { publicUrl } } = supabase.storage
  .from('avatars')
  .getPublicUrl('avatar.png')

// Download
const { data, error } = await supabase.storage
  .from('avatars')
  .download('avatar.png')

// Delete
const { error } = await supabase.storage
  .from('avatars')
  .remove(['avatar.png'])

Signed URLs

// Generate signed URL (temporary access)
const { data, error } = await supabase.storage
  .from('avatars')
  .createSignedUrl('avatar.png', 60) // 60 seconds

console.log(data.signedUrl)

Edge Functions

Creating Functions

// supabase/functions/hello-world/index.ts
import { serve } from 'https://deno.land/[email protected]/http/server.ts'

serve(async (req) => {
  // CORS headers
  if (req.method === 'OPTIONS') {
    return new Response('ok', {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
      },
    })
  }

  // Get auth token
  const authHeader = req.headers.get('Authorization')!
  
  // Verify user
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_ANON_KEY')!,
    { global: { headers: { Authorization: authHeader } } }
  )
  
  const { data: { user } } = await supabase.auth.getUser()

  return new Response(
    JSON.stringify({ 
      message: `Hello, ${user?.email || 'world'}!`,
      timestamp: new Date().toISOString()
    }),
    { 
      headers: { 'Content-Type': 'application/json' },
    }
  )
})

Deploying

# Deploy function
supabase functions deploy hello-world

# Deploy with secrets
supabase functions deploy hello-world --no-verify-jwt
supabase functions secret set API_KEY=xxx

Calling from Client

// Call edge function
const { data, error } = await supabase.functions.invoke('hello-world', {
  body: { name: 'John' }
})

Using with Frontend Frameworks

React

// Create client
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_ANON_KEY
)

// In components
function App() {
  const [posts, setPosts] = useState([])
  
  useEffect(() => {
    // Fetch posts
    supabase
      .from('posts')
      .select('*')
      .then(({ data }) => setPosts(data))
  }, [])
  
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Next.js (App Router)

// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export function createClient() {
  const cookieStore = cookies()
  
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value
        },
        set(name: string, value: string, options) {
          cookieStore.set(name, value, options)
        },
        remove(name: string, options) {
          cookieStore.delete(name, options)
        },
      },
    }
  )
}

Vue

// plugins/supabase.js
import { createClient } from '@supabase/supabase-js'

export default ({ app }, inject) => {
  const supabase = createClient(
    import.meta.env.VITE_SUPABASE_URL,
    import.meta.env.VITE_SUPABASE_ANON_KEY
  )
  
  app.config.globalProperties.$supabase = supabase
  inject('supabase', supabase)
}

Database Extensions

PostGIS (Geographic)

-- Enable PostGIS
CREATE EXTENSION postgis;

-- Create location table
CREATE TABLE locations (
  id BIGINT GENERATED BY DEFAULT ASID IDENTITY PRIMARY KEY,
  name TEXT,
  coordinates GEOGRAPHY(POINT, 4326)
);

-- Query nearby locations
SELECT * FROM locations
WHERE ST_DWithin(
  coordinates,
  ST_MakePoint(-122.4194, 37.7749)::geography,
  5000 -- 5km radius
);

Vector (Embeddings)

-- Enable vector extension
CREATE EXTENSION vector;

-- Create embedding column
ALTER TABLE posts ADD COLUMN embedding vector(1536);

-- Query similar
SELECT * FROM posts
ORDER BY embedding <=> query_embedding
LIMIT 5;

Supabase vs Firebase

Feature Supabase Firebase
Database PostgreSQL NoSQL
Query Language SQL NoSQL queries
Real-time PostgreSQL logical decoding Proprietary
Storage S3-compatible Google Cloud Storage
Auth GoTrue + PostgreSQL Proprietary
Open Source โœ… โŒ
Pricing Free tier + pay Free tier + pay
GraphQL PostgREST + GraphQL โŒ

Conclusion

Supabase is an excellent choice when you need:

  • PostgreSQL’s power with modern developer experience
  • Open source - self-host or use managed
  • SQL - full query capabilities
  • Real-time - built-in subscriptions
  • Scalability - from startup to enterprise

Perfect for: MVPs, startups, indie hackers, and teams that know SQL.


External Resources

Comments