JavaScript 8 min read

Supabase: Complete Backend-as-a-Service Guide

Master Supabase for full-stack development. Learn PostgreSQL database, authentication, real-time subscriptions, storage, and build modern applications.

MR

Moshiour Rahman

Advertisement

What is Supabase?

Supabase is an open-source Firebase alternative built on PostgreSQL. It provides database, authentication, real-time subscriptions, storage, and serverless functions in one platform.

Supabase Features

FeatureDescription
DatabasePostgreSQL with instant APIs
AuthEmail, OAuth, magic links
Real-timeWebSocket subscriptions
StorageFile uploads and CDN
Edge FunctionsServerless TypeScript

Getting Started

Installation

npm install @supabase/supabase-js

Client Setup

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

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseKey)

// With TypeScript types
import { Database } from './database.types'

export const supabase = createClient<Database>(supabaseUrl, supabaseKey)

Database Operations

Basic CRUD

// Create
const { data, error } = await supabase
  .from('posts')
  .insert({
    title: 'Hello World',
    content: 'My first post',
    author_id: userId
  })
  .select()
  .single()

// Read - single
const { data: post } = await supabase
  .from('posts')
  .select('*')
  .eq('id', postId)
  .single()

// Read - multiple with filtering
const { data: posts } = await supabase
  .from('posts')
  .select('*')
  .eq('published', true)
  .order('created_at', { ascending: false })
  .limit(10)

// Update
const { data, error } = await supabase
  .from('posts')
  .update({ title: 'Updated Title' })
  .eq('id', postId)
  .select()
  .single()

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

Advanced Queries

// Select specific columns with relations
const { data } = await supabase
  .from('posts')
  .select(`
    id,
    title,
    content,
    author:profiles(id, username, avatar_url),
    comments(id, content, created_at)
  `)
  .eq('published', true)

// Filtering
const { data } = await supabase
  .from('products')
  .select('*')
  .gt('price', 100)              // greater than
  .lt('price', 500)              // less than
  .gte('stock', 1)               // greater than or equal
  .in('category', ['electronics', 'gadgets'])
  .like('name', '%phone%')       // pattern matching
  .ilike('name', '%PHONE%')      // case insensitive
  .is('deleted_at', null)        // null check
  .neq('status', 'draft')        // not equal

// Full-text search
const { data } = await supabase
  .from('posts')
  .select('*')
  .textSearch('title', 'react hooks', {
    type: 'websearch',
    config: 'english'
  })

// Range queries
const { data } = await supabase
  .from('posts')
  .select('*')
  .range(0, 9)  // First 10 results

// OR conditions
const { data } = await supabase
  .from('posts')
  .select('*')
  .or('status.eq.published,status.eq.featured')

// Count
const { count } = await supabase
  .from('posts')
  .select('*', { count: 'exact', head: true })

Upsert and Transactions

// Upsert (insert or update)
const { data } = await supabase
  .from('users')
  .upsert({
    id: userId,
    email: 'user@example.com',
    updated_at: new Date().toISOString()
  })
  .select()

// Bulk insert
const { data } = await supabase
  .from('posts')
  .insert([
    { title: 'Post 1', content: 'Content 1' },
    { title: 'Post 2', content: 'Content 2' },
    { title: 'Post 3', content: 'Content 3' }
  ])
  .select()

// RPC (stored procedures)
const { data } = await supabase
  .rpc('increment_view_count', { post_id: postId })

Authentication

Email/Password Auth

// Sign up
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'securepassword',
  options: {
    data: {
      username: 'johndoe',
      full_name: 'John Doe'
    }
  }
})

// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'securepassword'
})

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

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

// Get session
const { data: { session } } = await supabase.auth.getSession()

OAuth Providers

// Sign in with OAuth
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',  // github, discord, twitter, etc.
  options: {
    redirectTo: 'http://localhost:3000/auth/callback'
  }
})

// Handle callback
// In your callback page
const { data, error } = await supabase.auth.exchangeCodeForSession(code)
// Send magic link
const { error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'http://localhost:3000/welcome'
  }
})

Auth State Management

// Listen to auth changes
supabase.auth.onAuthStateChange((event, session) => {
  console.log('Auth event:', event)
  console.log('Session:', session)

  if (event === 'SIGNED_IN') {
    // User signed in
  } else if (event === 'SIGNED_OUT') {
    // User signed out
  } else if (event === 'TOKEN_REFRESHED') {
    // Token was refreshed
  }
})

// React hook example
import { useEffect, useState } from 'react'
import { User } from '@supabase/supabase-js'

export function useUser() {
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Get initial session
    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null)
      setLoading(false)
    })

    // Listen for changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        setUser(session?.user ?? null)
      }
    )

    return () => subscription.unsubscribe()
  }, [])

  return { user, loading }
}

Real-time Subscriptions

Subscribe to Changes

// Subscribe to all changes on a table
const channel = supabase
  .channel('posts-channel')
  .on(
    'postgres_changes',
    { event: '*', schema: 'public', table: 'posts' },
    (payload) => {
      console.log('Change received!', payload)
    }
  )
  .subscribe()

// Subscribe to specific events
const channel = supabase
  .channel('posts-inserts')
  .on(
    'postgres_changes',
    { event: 'INSERT', schema: 'public', table: 'posts' },
    (payload) => {
      console.log('New post:', payload.new)
    }
  )
  .on(
    'postgres_changes',
    { event: 'UPDATE', schema: 'public', table: 'posts' },
    (payload) => {
      console.log('Updated post:', payload.new)
      console.log('Old values:', payload.old)
    }
  )
  .on(
    'postgres_changes',
    { event: 'DELETE', schema: 'public', table: 'posts' },
    (payload) => {
      console.log('Deleted post:', payload.old)
    }
  )
  .subscribe()

// Filter subscriptions
const channel = supabase
  .channel('user-posts')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'posts',
      filter: `author_id=eq.${userId}`
    },
    (payload) => {
      console.log('User post changed:', payload)
    }
  )
  .subscribe()

// Unsubscribe
await supabase.removeChannel(channel)

Broadcast Messages

// Create a channel for broadcasting
const channel = supabase.channel('room-1')

// Subscribe to broadcasts
channel
  .on('broadcast', { event: 'cursor-pos' }, ({ payload }) => {
    console.log('Cursor position:', payload)
  })
  .subscribe()

// Send broadcast
channel.send({
  type: 'broadcast',
  event: 'cursor-pos',
  payload: { x: 100, y: 200 }
})

Presence

// Track user presence
const channel = supabase.channel('online-users')

channel
  .on('presence', { event: 'sync' }, () => {
    const state = channel.presenceState()
    console.log('Online users:', state)
  })
  .on('presence', { event: 'join' }, ({ key, newPresences }) => {
    console.log('User joined:', newPresences)
  })
  .on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
    console.log('User left:', leftPresences)
  })
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({
        user_id: userId,
        username: 'johndoe',
        online_at: new Date().toISOString()
      })
    }
  })

Storage

File Operations

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

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

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

console.log(data.publicUrl)

// Create signed URL (temporary)
const { data, error } = await supabase.storage
  .from('private-files')
  .createSignedUrl('document.pdf', 3600) // 1 hour

// List files
const { data, error } = await supabase.storage
  .from('avatars')
  .list(userId, {
    limit: 100,
    offset: 0,
    sortBy: { column: 'created_at', order: 'desc' }
  })

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

// Move/rename file
const { error } = await supabase.storage
  .from('avatars')
  .move('old-path/file.png', 'new-path/file.png')

Row Level Security (RLS)

SQL Policies

-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Policy: Users can read all published posts
CREATE POLICY "Public posts are viewable by everyone"
ON posts FOR SELECT
USING (published = true);

-- Policy: Users can only insert their own posts
CREATE POLICY "Users can insert own posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);

-- Policy: Users can only update their own posts
CREATE POLICY "Users can update own posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id)
WITH CHECK (auth.uid() = author_id);

-- Policy: Users can only delete their own posts
CREATE POLICY "Users can delete own posts"
ON posts FOR DELETE
USING (auth.uid() = author_id);

-- Policy: Admins can do anything
CREATE POLICY "Admins have full access"
ON posts FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM profiles
    WHERE profiles.id = auth.uid()
    AND profiles.role = 'admin'
  )
);

Edge Functions

Create Function

// supabase/functions/hello-world/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'

serve(async (req) => {
  const { name } = await req.json()

  return new Response(
    JSON.stringify({ message: `Hello ${name}!` }),
    { headers: { 'Content-Type': 'application/json' } }
  )
})

// Call from client
const { data, error } = await supabase.functions.invoke('hello-world', {
  body: { name: 'World' }
})

React Integration

// hooks/usePosts.ts
import { useEffect, useState } from 'react'
import { supabase } from '../lib/supabase'

export function usePosts() {
  const [posts, setPosts] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetchPosts()

    // 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, ...prev])
          } else if (payload.eventType === 'DELETE') {
            setPosts(prev => prev.filter(p => p.id !== payload.old.id))
          } else if (payload.eventType === 'UPDATE') {
            setPosts(prev => prev.map(p =>
              p.id === payload.new.id ? payload.new : p
            ))
          }
        }
      )
      .subscribe()

    return () => {
      supabase.removeChannel(channel)
    }
  }, [])

  async function fetchPosts() {
    const { data } = await supabase
      .from('posts')
      .select('*')
      .order('created_at', { ascending: false })

    setPosts(data || [])
    setLoading(false)
  }

  return { posts, loading }
}

Summary

FeatureUse Case
DatabasePostgreSQL with REST API
AuthUser authentication
Real-timeLive updates
StorageFile uploads
Edge FunctionsServerless compute
RLSRow-level security

Supabase provides a complete backend solution for modern applications with PostgreSQL at its core.

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.