DevToolBoxGRATIS
Blogg

Supabase Guide 2026: Authentication, Database, Real-time, Storage & Edge Functions

20 min readby DevToolBox
TL;DR

Supabase is an open-source Firebase alternative built on PostgreSQL. It provides a hosted Postgres database with Row Level Security, authentication (email, OAuth, magic links), real-time subscriptions via WebSockets, file storage with CDN, Edge Functions (Deno-based serverless), and auto-generated REST/GraphQL APIs. Use the @supabase/supabase-js client for full TypeScript support and generate types directly from your database schema.

What Is Supabase?

Supabase is an open-source Backend-as-a-Service (BaaS) built on top of PostgreSQL. It provides a suite of tools that replaces Firebase for projects that need a relational database. Unlike Firebase which uses a proprietary NoSQL database, Supabase gives you a full Postgres database with SQL support, joins, foreign keys, and advanced indexing.

Supabase vs Firebase Comparison

FeatureSupabaseFirebase
DatabasePostgreSQL (relational, SQL)Firestore (NoSQL, document)
AuthEmail, OAuth 20+, magic links, phoneEmail, OAuth, phone, anonymous
Real-timePostgres Changes, Broadcast, PresenceFirestore listeners, RTDB
StorageS3-compatible, CDN, transformsCloud Storage, CDN
FunctionsEdge Functions (Deno)Cloud Functions (Node.js)
Open SourceYes (fully open-source)No (proprietary)
Self-hostingYes (Docker Compose)No
PricingFree: 500MB DB, Pro: $25/moFree: limited, Blaze: pay-as-you-go
Query LanguageSQL, PostgREST, GraphQLProprietary SDK queries
Vendor Lock-inLow (standard Postgres)High (proprietary formats)

Setting Up a Supabase Project

You can start with the Supabase cloud platform or self-host using Docker. The cloud platform offers a generous free tier with 500MB database, 1GB file storage, and 50,000 monthly active users.

# Install Supabase CLI
npm install -g supabase

# Login to Supabase
supabase login

# Create a new project locally
supabase init

# Start local development (Docker required)
supabase start

# Output:
#   API URL:   http://localhost:54321
#   DB URL:    postgresql://postgres:postgres@localhost:54322/postgres
#   Studio:    http://localhost:54323
#   anon key:  eyJhbG...
#   service_role key: eyJhbG...

# Install the JavaScript client
npm install @supabase/supabase-js
// lib/supabase.ts - Initialize the client
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

// For server-side admin operations (never expose to client)
import { createClient as createAdmin } from '@supabase/supabase-js';

const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
export const supabaseAdmin = createAdmin(supabaseUrl, serviceRoleKey);

Database: PostgreSQL with Row Level Security

Supabase gives you a full PostgreSQL database. You write standard SQL for migrations and use Row Level Security (RLS) to control access at the row level. RLS policies are evaluated on every query, ensuring that users can only see and modify data they are authorized to access.

Creating Tables and Migrations

-- supabase/migrations/001_create_tables.sql

-- Profiles table (extends auth.users)
CREATE TABLE public.profiles (
  id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
  username TEXT UNIQUE NOT NULL,
  full_name TEXT,
  avatar_url TEXT,
  bio TEXT,
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
);

-- Posts table
CREATE TABLE public.posts (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  author_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
  title TEXT NOT NULL,
  content TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  published BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
);

-- Comments table
CREATE TABLE public.comments (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  post_id BIGINT REFERENCES public.posts(id) ON DELETE CASCADE NOT NULL,
  user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
  content TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL
);

-- Indexes for common queries
CREATE INDEX idx_posts_author ON public.posts(author_id);
CREATE INDEX idx_posts_slug ON public.posts(slug);
CREATE INDEX idx_posts_published ON public.posts(published) WHERE published = true;
CREATE INDEX idx_comments_post ON public.comments(post_id);

-- Auto-update updated_at column
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER profiles_updated_at
  BEFORE UPDATE ON public.profiles
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER posts_updated_at
  BEFORE UPDATE ON public.posts
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

Row Level Security (RLS)

RLS is the core security model in Supabase. When enabled on a table, every query is filtered by the policies you define. Without RLS, anyone with the anon key can read all data in a table.

-- Enable RLS on all tables
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.comments ENABLE ROW LEVEL SECURITY;

-- Profiles: anyone can read, owners can update their own
CREATE POLICY "Public profiles are viewable by everyone"
  ON public.profiles FOR SELECT
  USING (true);

CREATE POLICY "Users can update their own profile"
  ON public.profiles FOR UPDATE
  USING (auth.uid() = id)
  WITH CHECK (auth.uid() = id);

-- Posts: published posts visible to all, authors manage own posts
CREATE POLICY "Published posts are viewable by everyone"
  ON public.posts FOR SELECT
  USING (published = true OR auth.uid() = author_id);

CREATE POLICY "Users can create posts"
  ON public.posts FOR INSERT
  WITH CHECK (auth.uid() = author_id);

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

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

-- Comments: anyone reads, authenticated users create, owners delete
CREATE POLICY "Comments are viewable by everyone"
  ON public.comments FOR SELECT
  USING (true);

CREATE POLICY "Authenticated users can create comments"
  ON public.comments FOR INSERT
  WITH CHECK (auth.uid() = user_id);

CREATE POLICY "Users can delete their own comments"
  ON public.comments FOR DELETE
  USING (auth.uid() = user_id);

Querying Data with the Client

The Supabase JavaScript client provides a fluent query builder that maps to PostgREST queries under the hood.

// Fetch published posts with author profile
const { data: posts, error } = await supabase
  .from('posts')
  .select('
    id, title, slug, content, created_at,
    profiles:author_id (username, full_name, avatar_url)
  ')
  .eq('published', true)
  .order('created_at', { ascending: false })
  .limit(10);

// Fetch a single post by slug with comments
const { data: post } = await supabase
  .from('posts')
  .select('
    *, 
    profiles:author_id (username, avatar_url),
    comments (id, content, created_at, profiles:user_id (username))
  ')
  .eq('slug', 'my-first-post')
  .single();

// Insert a new post
const { data, error } = await supabase
  .from('posts')
  .insert({
    author_id: user.id,
    title: 'My New Post',
    content: 'Post content here...',
    slug: 'my-new-post',
    published: true,
  })
  .select()
  .single();

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

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

// Full-text search
const { data } = await supabase
  .from('posts')
  .select('id, title, slug')
  .textSearch('title', 'supabase & guide');

// Pagination with range
const { data, count } = await supabase
  .from('posts')
  .select('*', { count: 'exact' })
  .eq('published', true)
  .range(0, 9); // rows 0-9 (first 10)

Authentication

Supabase Auth supports email/password, magic links, phone OTP, and over 20 OAuth providers including Google, GitHub, Discord, and Apple. It manages user sessions with JWTs and integrates directly with RLS policies via the auth.uid() function.

Email and Password Authentication

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

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

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

// Sign out
const { error } = await supabase.auth.signOut();

// Listen to auth state changes
supabase.auth.onAuthStateChange((event, session) => {
  console.log('Auth event:', event);
  // event: SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, etc.
  if (session) {
    console.log('User:', session.user.email);
  }
});

OAuth Providers (Google, GitHub)

// Sign in with Google
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://yourapp.com/auth/callback',
    queryParams: {
      access_type: 'offline',
      prompt: 'consent',
    },
  },
});

// Sign in with GitHub
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: {
    redirectTo: 'https://yourapp.com/auth/callback',
    scopes: 'read:user user:email',
  },
});

// Handle OAuth callback (in your callback route)
// app/auth/callback/route.ts
import { NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';

export async function GET(request: Request) {
  const url = new URL(request.url);
  const code = url.searchParams.get('code');

  if (code) {
    const supabase = createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
    );
    await supabase.auth.exchangeCodeForSession(code);
  }

  return NextResponse.redirect(new URL('/dashboard', request.url));
}

Magic Link Authentication

// Send magic link email
const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://yourapp.com/auth/callback',
  },
});

// Phone OTP
const { data, error } = await supabase.auth.signInWithOtp({
  phone: '+1234567890',
});

// Verify phone OTP
const { data, error } = await supabase.auth.verifyOtp({
  phone: '+1234567890',
  token: '123456',
  type: 'sms',
});

Protecting Routes with Auth Middleware

// middleware.ts - Protect routes with Supabase Auth
import { createServerClient } from '@supabase/ssr';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({
    request: { headers: request.headers },
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll();
        },
        setAll(cookies) {
          cookies.forEach(({ name, value, options }) => {
            response.cookies.set(name, value, options);
          });
        },
      },
    }
  );

  const { data: { user } } = await supabase.auth.getUser();

  // Redirect unauthenticated users to login
  if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return response;
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
};

Real-time Subscriptions

Supabase Realtime lets you listen to database changes (INSERT, UPDATE, DELETE) via WebSocket connections. It supports Postgres Changes, Broadcast (pub/sub messaging), and Presence (track online users). Realtime respects your RLS policies, so users only receive changes they are authorized to see.

Listening to Database Changes

// Listen to all changes on the posts table
const channel = supabase
  .channel('posts-changes')
  .on(
    'postgres_changes',
    {
      event: '*',       // INSERT, UPDATE, DELETE, or *
      schema: 'public',
      table: 'posts',
    },
    (payload) => {
      console.log('Change received:', payload);
      // payload.eventType: INSERT | UPDATE | DELETE
      // payload.new: new row data (INSERT/UPDATE)
      // payload.old: old row data (UPDATE/DELETE)
    }
  )
  .subscribe();

// Listen to inserts on a specific user's posts
const channel2 = supabase
  .channel('my-posts')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'comments',
      filter: 'post_id=eq.42',
    },
    (payload) => {
      console.log('New comment:', payload.new);
    }
  )
  .subscribe();

// Unsubscribe when done
supabase.removeChannel(channel);

Broadcast: Pub/Sub Messaging

// Broadcast: send messages to all connected clients
// Useful for cursor positions, typing indicators, notifications

const channel = supabase.channel('room-1');

// Listen for broadcast messages
channel
  .on('broadcast', { event: 'cursor-move' }, (payload) => {
    console.log('Cursor:', payload.payload);
  })
  .subscribe();

// Send a broadcast message
channel.send({
  type: 'broadcast',
  event: 'cursor-move',
  payload: { x: 100, y: 200, userId: 'abc123' },
});

Presence: Track Online Users

// Presence: track who is online in a room
const channel = supabase.channel('room-1');

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

Storage: File Uploads

Supabase Storage manages files (images, videos, documents) in S3-compatible buckets with CDN caching. Buckets can be public or private. Private buckets require signed URLs for access. Storage integrates with RLS for fine-grained access control.

Uploading and Downloading Files

// Upload a file to a public bucket
const file = event.target.files[0];
const fileName = Date.now() + '-' + file.name;

const { data, error } = await supabase.storage
  .from('avatars')
  .upload('public/' + fileName, file, {
    cacheControl: '3600',
    upsert: false,
    contentType: file.type,
  });

// Get public URL
const { data: urlData } = supabase.storage
  .from('avatars')
  .getPublicUrl('public/' + fileName);

console.log('Public URL:', urlData.publicUrl);

// Download a file
const { data: blob, error } = await supabase.storage
  .from('avatars')
  .download('public/' + fileName);

// Create a signed URL for private files (expires in 1 hour)
const { data: signed, error } = await supabase.storage
  .from('documents')
  .createSignedUrl('private/report.pdf', 3600);

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

// Delete a file
const { error } = await supabase.storage
  .from('avatars')
  .remove(['public/' + fileName]);

// Image transforms (resize on the fly)
const { data: transformed } = supabase.storage
  .from('avatars')
  .getPublicUrl('public/photo.jpg', {
    transform: {
      width: 200,
      height: 200,
      resize: 'cover',
      quality: 80,
    },
  });

Storage Security Policies

-- Storage policies for the avatars bucket

-- Allow public read access
CREATE POLICY "Avatar images are publicly accessible"
  ON storage.objects FOR SELECT
  USING (bucket_id = 'avatars');

-- Allow authenticated users to upload their own avatar
CREATE POLICY "Users can upload their own avatar"
  ON storage.objects FOR INSERT
  WITH CHECK (
    bucket_id = 'avatars'
    AND auth.uid()::text = (storage.foldername(name))[1]
  );

-- Allow users to update their own avatar
CREATE POLICY "Users can update their own avatar"
  ON storage.objects FOR UPDATE
  USING (
    bucket_id = 'avatars'
    AND auth.uid()::text = (storage.foldername(name))[1]
  );

-- Allow users to delete their own avatar
CREATE POLICY "Users can delete their own avatar"
  ON storage.objects FOR DELETE
  USING (
    bucket_id = 'avatars'
    AND auth.uid()::text = (storage.foldername(name))[1]
  );

Edge Functions

Supabase Edge Functions are server-side TypeScript functions powered by Deno. They run close to users on the edge network and are ideal for webhooks, third-party API integrations, and custom server logic that cannot run on the client.

Creating and Deploying Edge Functions

# Create a new Edge Function
supabase functions new send-email

# Project structure:
# supabase/functions/send-email/index.ts

# Deploy the function
supabase functions deploy send-email

# Deploy all functions
supabase functions deploy

# Test locally
supabase functions serve send-email --no-verify-jwt
// supabase/functions/send-email/index.ts
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

serve(async (req) => {
  // Handle CORS preflight
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders });
  }

  try {
    const { to, subject, body } = await req.json();

    // Call an email API (e.g., Resend)
    const res = await fetch('https://api.resend.com/emails', {
      method: 'POST',
      headers: {
        Authorization: 'Bearer ' + Deno.env.get('RESEND_API_KEY'),
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        from: 'noreply@yourapp.com',
        to: to,
        subject: subject,
        html: body,
      }),
    });

    const data = await res.json();

    return new Response(JSON.stringify(data), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 200,
    });
  } catch (err) {
    return new Response(JSON.stringify({ error: err.message }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      status: 500,
    });
  }
});

Using Database and Auth in Edge Functions

// supabase/functions/process-order/index.ts
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  // Create Supabase client with service role (admin access)
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
  );

  // Get user from auth header
  const authHeader = req.headers.get('Authorization')!;
  const token = authHeader.replace('Bearer ', '');
  const { data: { user } } = await supabase.auth.getUser(token);

  if (!user) {
    return new Response('Unauthorized', { status: 401 });
  }

  const { orderId } = await req.json();

  // Use admin client to bypass RLS for server operations
  const { data: order, error } = await supabase
    .from('orders')
    .update({ status: 'processing', processed_at: new Date().toISOString() })
    .eq('id', orderId)
    .eq('user_id', user.id)
    .select()
    .single();

  return new Response(JSON.stringify({ order }), {
    headers: { 'Content-Type': 'application/json' },
  });
});
// Invoke Edge Function from the client
const { data, error } = await supabase.functions.invoke('send-email', {
  body: {
    to: 'user@example.com',
    subject: 'Welcome!',
    body: '<h1>Welcome to our app</h1>',
  },
});

// Invoke with custom headers
const { data, error } = await supabase.functions.invoke('process-order', {
  body: { orderId: 'order_123' },
  headers: { 'x-custom-header': 'value' },
});

TypeScript Integration and Type Generation

Supabase provides first-class TypeScript support. You can generate types directly from your database schema using the Supabase CLI, giving you full type safety for all queries, inserts, and updates.

Generating Types from Database Schema

# Generate types from your remote database
supabase gen types typescript \
  --project-id your-project-id \
  > src/types/database.ts

# Or from a local database
supabase gen types typescript --local > src/types/database.ts

# Add to package.json scripts
# "gen-types": "supabase gen types typescript --project-id abc123 > src/types/database.ts"
// Generated types look like this (src/types/database.ts)
export type Database = {
  public: {
    Tables: {
      profiles: {
        Row: {
          id: string;
          username: string;
          full_name: string | null;
          avatar_url: string | null;
          bio: string | null;
          created_at: string;
          updated_at: string;
        };
        Insert: {
          id: string;
          username: string;
          full_name?: string | null;
          avatar_url?: string | null;
          bio?: string | null;
        };
        Update: {
          username?: string;
          full_name?: string | null;
          avatar_url?: string | null;
          bio?: string | null;
        };
      };
      posts: {
        Row: {
          id: number;
          author_id: string;
          title: string;
          content: string;
          slug: string;
          published: boolean;
          created_at: string;
          updated_at: string;
        };
        Insert: {
          author_id: string;
          title: string;
          content: string;
          slug: string;
          published?: boolean;
        };
        Update: {
          title?: string;
          content?: string;
          slug?: string;
          published?: boolean;
        };
      };
    };
  };
};

Using Generated Types in Queries

// lib/supabase.ts - Typed client
import { createClient } from '@supabase/supabase-js';
import type { Database } from '@/types/database';

export const supabase = createClient<Database>(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// Now all queries are fully typed
// data is typed as Database["public"]["Tables"]["posts"]["Row"][]
const { data: posts } = await supabase
  .from('posts')
  .select('id, title, slug, published');

// TypeScript enforces correct insert shape
const { data } = await supabase
  .from('posts')
  .insert({
    author_id: user.id,    // required: string
    title: 'My Post',      // required: string
    content: 'Content',    // required: string
    slug: 'my-post',       // required: string
    // published is optional (defaults to false)
  })
  .select()
  .single();

// Helper types for reuse
type Post = Database['public']['Tables']['posts']['Row'];
type PostInsert = Database['public']['Tables']['posts']['Insert'];
type PostUpdate = Database['public']['Tables']['posts']['Update'];
type Profile = Database['public']['Tables']['profiles']['Row'];

// Use in React components
function PostCard({ post }: { post: Post }) {
  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
      <time>{new Date(post.created_at).toLocaleDateString()}</time>
    </div>
  );
}

Best Practices for Production

  • Always enable RLS on every table: even internal tables should have policies
  • Use database migrations (supabase db diff) for all schema changes, never modify production directly
  • Create database indexes on columns used in WHERE, ORDER BY, and JOIN clauses
  • Use connection pooling (Supavisor) for serverless environments with many short-lived connections
  • Store the service_role key only on the server side, never expose it to the client
  • Use Edge Functions for sensitive operations like payment processing and email sending
  • Set up database backups and point-in-time recovery for production projects
  • Monitor query performance with pg_stat_statements and the Supabase dashboard
  • Use storage transform for on-the-fly image resizing instead of storing multiple sizes
  • Implement rate limiting on auth endpoints using Supabase rate limits or Edge Functions
-- Production-ready database setup checklist

-- 1. Enable RLS on ALL tables
DO $$
DECLARE
  t RECORD;
BEGIN
  FOR t IN SELECT tablename FROM pg_tables WHERE schemaname = 'public'
  LOOP
    EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t.tablename);
  END LOOP;
END $$;

-- 2. Create indexes for performance
CREATE INDEX CONCURRENTLY idx_posts_created
  ON public.posts(created_at DESC);

-- 3. Enable pg_stat_statements for query monitoring
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- 4. Check for missing indexes
SELECT
  relname AS table_name,
  seq_scan - idx_scan AS too_many_seq_scans,
  pg_size_pretty(pg_relation_size(relid)) AS table_size
FROM pg_stat_user_tables
WHERE seq_scan - idx_scan > 0
ORDER BY too_many_seq_scans DESC
LIMIT 10;

Frequently Asked Questions

Is Supabase a good alternative to Firebase?

Yes. Supabase provides equivalent features (auth, database, storage, real-time, serverless functions) but uses PostgreSQL instead of a NoSQL database. This makes it better for apps needing relational data, complex queries, and SQL joins. Supabase is open-source and can be self-hosted.

How much does Supabase cost?

The free tier includes 500MB database, 1GB file storage, 50K monthly active users, and 500K Edge Function invocations. The Pro plan at $25/month adds 8GB database, 100GB storage, and higher limits. Enterprise plans offer dedicated infrastructure and SLAs.

Can I self-host Supabase?

Yes. Supabase is fully open-source and provides Docker Compose files for self-hosting. You get all features including Auth, Realtime, Storage, and the dashboard. Self-hosting requires managing PostgreSQL, but gives you full data ownership.

How does Row Level Security work in Supabase?

RLS is a PostgreSQL feature that filters every query based on policies you define. Policies use SQL expressions to check conditions like auth.uid() = user_id. When RLS is enabled, rows that do not match any policy are invisible. This provides database-level access control without server middleware.

Does Supabase support real-time subscriptions?

Yes. Supabase Realtime supports three modes: Postgres Changes (listen to INSERT/UPDATE/DELETE on tables), Broadcast (pub/sub messaging between clients), and Presence (track which users are online). All modes use WebSocket connections and respect RLS policies.

What are Supabase Edge Functions?

Edge Functions are server-side TypeScript/JavaScript functions powered by Deno that run on the edge network. They handle tasks like webhooks, API integrations, Stripe payments, and custom auth logic. They can access the database using the service_role key for admin operations.

How do I generate TypeScript types from Supabase?

Run supabase gen types typescript --project-id your-project-id > src/types/database.ts. This generates types for all tables, views, and functions. Use the Database type with the Supabase client for full type safety on queries, inserts, and updates.

Can Supabase handle production workloads?

Yes. Supabase runs on PostgreSQL which powers some of the largest applications in the world. The Pro and Enterprise plans include connection pooling, read replicas, daily backups, and point-in-time recovery. Many production applications including large SaaS platforms use Supabase.

Key Takeaways
  • Supabase is built on PostgreSQL: you get full SQL, joins, indexes, and ACID transactions
  • Row Level Security is the primary access control mechanism: enable it on every table
  • Auth supports email, OAuth (20+ providers), magic links, and phone OTP out of the box
  • Realtime subscriptions support database changes, broadcast, and presence tracking
  • Edge Functions (Deno-based) handle server-side logic without managing infrastructure
  • Generate TypeScript types from your schema for end-to-end type safety
  • Use connection pooling, database indexes, and migrations for production readiness
𝕏 Twitterin LinkedIn
Var detta hjälpsamt?

Håll dig uppdaterad

Få veckovisa dev-tips och nya verktyg.

Ingen spam. Avsluta när som helst.

Try These Related Tools

{ }JSON FormatterJWTJWT DecoderIDUUID Generator

Related Articles

OAuth 2.0 & Authentication Guide: PKCE, JWT, OpenID Connect, RBAC & Security Best Practices

Complete OAuth 2.0 and authentication guide covering authorization code flow with PKCE, JWT tokens, OpenID Connect, session management, RBAC, social login, MFA, token refresh, CSRF protection, rate limiting, and security best practices.

Next.js Advanced Guide: App Router, Server Components, Data Fetching, Middleware & Performance

Complete Next.js advanced guide covering App Router architecture, React Server Components, streaming SSR, data fetching patterns, middleware, route handlers, parallel and intercepting routes, caching strategies, ISR, image optimization, and deployment best practices.

PostgreSQL Complete Guide: SQL, Indexes, JSONB, and Performance

Master PostgreSQL with this complete guide. Covers core SQL, indexes, Node.js pg, Prisma ORM, Python asyncpg, JSONB, full-text search, window functions, and performance tuning.