Kazi Rahamatullah
Kazi Rahamatullah
AboutProjectsBlogContact
UsesBooks
ResumeView CV
Kazi Rahamatullah

© Copyright 2026 Kazi Rahamatullah

AboutProjectsBlogBooksUses
Twitter/XGitHubProduct HuntCodeSandbox
Back to Blog
SupabasePostgreSQLBackendAuthenticationReact

Supabase Complete Crash Course

Master Supabase — Postgres architecture, local dev with CLI, CRUD, Auth, Row Level Security, Realtime, Storage, and React integration with production patterns.

Jul 3, 202614 min read
Supabase Complete Crash Course

Introduction

Supabase is an open-source Firebase alternative built on PostgreSQL. You get a managed database, auto-generated REST and GraphQL APIs, authentication, row-level security, realtime subscriptions, and file storage — all wired together with a single project dashboard and JavaScript SDK.

Goal: learn to build full-stack apps with Postgres as a service and Supabase's complete feature set.

This guide covers every branch from the crash course mind map:

  1. Core architecture — Postgres, API layer, GoTrue, Kong, realtime security
  2. Hosting & deployment — Supabase Cloud vs self-hosting
  3. Local development — Docker, CLI, local Studio
  4. Database management — Table Editor, SQL Editor, schema, CRUD
  5. Authentication — sign up/in, SMTP, magic links, sessions, listeners
  6. Security (RLS) — policies for public, authenticated, and owner access
  7. Advanced features — Realtime subscriptions and Storage buckets
  8. Frontend integration — React SDK, env vars, hooks
  9. Best practices — production checklist

Screenshot topic coverage map

Mind-map branchSectionKey topics included
Core Architecture§1Postgres, auto-API, GoTrue, Kong, built-in security
Hosting & Deployment§2Supabase Cloud PaaS vs self-hosted Docker
Local Development§3Docker Desktop, CLI, supabase init, local Studio
Database Management§4Table Editor, SQL Editor, schema, Select/Insert/Update/Delete
Authentication System§5Email, OAuth, magic links, SMTP, sessions, listeners
Security (RLS)§6Row Level Security, public / auth / owner policies
Advanced Features§7Realtime postgres changes, broadcast, Storage buckets
Frontend Integration§8@supabase/supabase-js, env vars, React hooks
Best & Recommended§9.env, RLS always on, service role key safety

Note

Supabase is PostgreSQL under the hood — anything you learn in SQL transfers directly. The platform adds Auth, Storage, and Realtime as managed services on top of standard Postgres.

Quick index

#Section
1Core architecture
2Hosting & deployment
3Local development setup
4Database management & CRUD
5Authentication system
6Row Level Security
7Realtime & Storage
8React frontend integration
9Best practices

1. Core architecture

Supabase is not a single service — it is a platform of integrated components:

plaintext
Your App (React / Next.js)
        ↓
  Supabase Client SDK
        ↓
┌───────────────────────────────────────┐
│  Kong API Gateway                     │
│  ├── PostgREST  → auto REST API     │
│  ├── GoTrue     → Auth (JWT)        │
│  ├── Realtime   → WebSocket changes │
│  └── Storage    → file buckets      │
└───────────────────────────────────────┘
        ↓
  PostgreSQL Database
ComponentRole
PostgreSQLCore database — tables, relations, SQL, extensions
Auto-generated APIPostgREST exposes tables as REST endpoints instantly
GoTrueAuth server — JWT tokens, OAuth, magic links
KongAPI gateway routing requests to services
RealtimeListens to Postgres WAL, broadcasts changes
StorageS3-compatible file buckets with RLS policies
Built-in securityRow Level Security enforced at the database layer

Tip

Think of Supabase as Postgres + batteries included. You can use only the database, or layer Auth, Realtime, and Storage as needed — no vendor lock-in on the SQL layer.

Back to index


2. Hosting & deployment

Supabase Cloud (managed PaaS)

BenefitDetail
Zero infrastructureNo Docker, no VPS management
Fast prototypingProject live in minutes at supabase.com
Managed backups & scalingHandled by Supabase team
Dashboard includedTable Editor, Auth settings, SQL Editor

Best for: MVPs, startups, solo developers, and teams that want managed Postgres without DevOps overhead.

Self-hosting (full data control)

Run the entire Supabase stack on your own VPS with Docker:

bash
git clone https://github.com/supabase/supabase
cd supabase/docker
cp .env.example .env
docker compose up -d
BenefitTrade-off
Full data sovereigntyYou manage updates, backups, scaling
Custom VPS / on-premOperational responsibility
Docker containersRequires Docker Desktop or Linux server

Warning

Self-hosting means you handle security patches, SSL certificates, backups, and uptime. Supabase Cloud is recommended until you have a dedicated DevOps reason to self-host.

Use caseRecommendation
Side project / MVPSupabase Cloud free tier
Production SaaSSupabase Cloud Pro
Regulated / on-premSelf-hosted Docker
Existing PostgresSupabase CLI migrations only

Back to index


3. Local development setup

Develop against a local Supabase stack that mirrors production — same Postgres, Auth, and API layer.

Prerequisites

ToolPurpose
Docker DesktopRuns local Supabase containers
Node.jsFrontend / CLI tooling
GitVersion-control migrations
Supabase CLILocal dev, migrations, types

Install CLI & initialize

bash
npm install -g supabase
# or: brew install supabase/tap/supabase
 
supabase login
supabase init          # creates supabase/ folder in project
supabase start         # starts local Docker stack

After supabase start, the CLI prints local credentials:

plaintext
API URL:     http://127.0.0.1:54321
DB URL:      postgresql://postgres:postgres@127.0.0.1:54322/postgres
Studio URL:  http://127.0.0.1:54323
anon key:    eyJ...
service_role key: eyJ...

Local Supabase Studio

Open Studio URL in the browser — same dashboard as Cloud (Table Editor, SQL Editor, Auth users, Storage) running locally.

bash
supabase stop          # stop containers
supabase db reset      # wipe + re-run migrations + seed
supabase status        # show running URLs and keys

Tip

Add supabase/.temp/ and local env files to .gitignore. Commit supabase/migrations/ to Git — same pattern as Prisma migrations.

Back to index


4. Database management & CRUD

Schema design with SQL migration

sql
-- supabase/migrations/20260703000000_create_posts.sql
create table public.posts (
  id          uuid primary key default gen_random_uuid(),
  title       text not null,
  content     text,
  published   boolean default false,
  author_id   uuid references auth.users(id) on delete cascade,
  created_at  timestamptz default now(),
  updated_at  timestamptz default now()
);
 
create index posts_author_id_idx on public.posts (author_id);

Apply locally:

bash
supabase db push
# or: supabase migration up

Table Editor vs SQL Editor

ToolBest for
Table EditorVisual CRUD, quick prototyping, non-devs
SQL EditorComplex queries, migrations, RLS policies

CRUD with the JavaScript client

typescript
import { createClient } from '@supabase/supabase-js'
 
const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!)
 
// SELECT — fetch data
const { data: posts, error } = await supabase
  .from('posts')
  .select('id, title, content, created_at')
  .eq('published', true)
  .order('created_at', { ascending: false })
  .limit(10)
 
// INSERT — add rows
const { data: newPost, error: insertError } = await supabase
  .from('posts')
  .insert({ title: 'Hello Supabase', content: 'First post', author_id: userId })
  .select()
  .single()
 
// UPDATE — modify data
await supabase.from('posts').update({ title: 'Updated title', updated_at: new Date().toISOString() }).eq('id', postId)
 
// DELETE — remove rows
await supabase.from('posts').delete().eq('id', postId)

Filter operators

typescript
const { data } = await supabase
  .from('posts')
  .select('*')
  .eq('published', true) // equals
  .neq('author_id', userId) // not equal
  .gt('created_at', '2026-01-01') // greater than
  .ilike('title', '%supabase%') // case-insensitive search
  .in('id', ['uuid-1', 'uuid-2']) // in list

Note

The Supabase client uses PostgREST under the hood — .from('table') maps directly to your Postgres table. Column names must match exactly (snake_case by convention).

Back to index


5. Authentication system

Supabase Auth (GoTrue) handles sign-up, sign-in, sessions, and JWT tokens — integrated with Postgres auth.users.

Email & password sign up

typescript
const { data, error } = await supabase.auth.signUp({
  email: 'kazi@example.com',
  password: 'secure-password-here',
  options: {
    emailRedirectTo: `${window.location.origin}/auth/callback`,
  },
})

Sign in

typescript
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'kazi@example.com',
  password: 'secure-password-here',
})

Magic link (passwordless)

typescript
await supabase.auth.signInWithOtp({
  email: 'kazi@example.com',
  options: { emailRedirectTo: `${window.location.origin}/auth/callback` },
})

OAuth — Google, GitHub, etc.

typescript
await supabase.auth.signInWithOAuth({
  provider: 'github',
  options: { redirectTo: `${window.location.origin}/auth/callback` },
})

Enable providers in Dashboard → Authentication → Providers.

SMTP & email verification

Configure a custom SMTP provider (SendGrid, Resend, AWS SES) in Dashboard → Authentication → Email Templates for production deliverability. Supabase sends confirmation and magic-link emails through this provider.

Warning

The default Supabase email sender has rate limits on the free tier. Configure custom SMTP before launching to real users — otherwise verification emails may not arrive reliably.

Session persistence & auth state listener

typescript
// Get current session on load
const {
  data: { session },
} = await supabase.auth.getSession()
 
// Listen for auth changes (login, logout, token refresh)
supabase.auth.onAuthStateChange((event, session) => {
  console.log(event, session?.user?.email)
  if (event === 'SIGNED_IN') {
    /* redirect to dashboard */
  }
  if (event === 'SIGNED_OUT') {
    /* redirect to login */
  }
})
 
// Sign out
await supabase.auth.signOut()

Note

Sessions are stored in localStorage (browser) or cookies (SSR). JWT access tokens expire — Supabase auto-refreshes them via onAuthStateChange and getSession().

Back to index


6. Row Level Security (RLS)

RLS is Supabase's core security model — Postgres enforces access at the row level, not in application code. Every table exposed via the API should have RLS enabled.

sql
-- Enable RLS on the table
alter table public.posts enable row level security;

Public read access

Anyone (including unauthenticated) can read published posts:

sql
create policy "Public posts are viewable by everyone"
  on public.posts for select
  using (published = true);

Authenticated users only

Only logged-in users can insert:

sql
create policy "Authenticated users can create posts"
  on public.posts for insert
  to authenticated
  with check (auth.uid() = author_id);

User-specific (owner) access

Users can only update or delete their own posts:

sql
create policy "Users can update own posts"
  on public.posts for update
  to authenticated
  using (auth.uid() = author_id)
  with check (auth.uid() = author_id);
 
create policy "Users can delete own posts"
  on public.posts for delete
  to authenticated
  using (auth.uid() = author_id);
Policy typeusing clauseTypical use
Public readpublished = trueBlog posts, product catalog
Authenticatedto authenticatedAny logged-in user can insert
Owner-onlyauth.uid() = author_idProfile, private drafts
Admin roleCustom JWT claim checkRole-based access

Warning

Never expose the service_role key in frontend code. It bypasses RLS entirely. Use it only in server-side route handlers, Edge Functions, or cron jobs.

Tip

Test RLS policies in the SQL Editor by running queries as different roles:

sql
set request.jwt.claim.sub = 'user-uuid-here';
select * from public.posts;

Back to index


7. Realtime & Storage

Realtime — Postgres changes subscription

Subscribe to INSERT, UPDATE, DELETE events on a table:

typescript
const channel = supabase
  .channel('posts-changes')
  .on('postgres_changes', { event: '*', schema: 'public', table: 'posts' }, (payload) => {
    console.log('Change:', payload.eventType, payload.new)
    // Update React state for live UI sync
  })
  .subscribe()
 
// Cleanup on unmount
channel.unsubscribe()

Enable Realtime on a table in Dashboard → Database → Replication or:

sql
alter publication supabase_realtime add table public.posts;

Broadcast — client-to-client messages

typescript
const room = supabase.channel('room:lobby')
 
room.on('broadcast', { event: 'cursor-move' }, ({ payload }) => {
  console.log('Cursor:', payload)
})
 
room.subscribe(async (status) => {
  if (status === 'SUBSCRIBED') {
    room.send({ type: 'broadcast', event: 'cursor-move', payload: { x: 100, y: 200 } })
  }
})

Storage — file uploads

Create buckets in Dashboard → Storage (public or private):

typescript
// Upload file
const { data, error } = await supabase.storage.from('avatars').upload(`public/${userId}.png`, file, { upsert: true })
 
// Public URL (public buckets only)
const {
  data: { publicUrl },
} = supabase.storage.from('avatars').getPublicUrl(`public/${userId}.png`)
 
// Private bucket — signed URL (expires)
const { data: signed } = await supabase.storage.from('private-docs').createSignedUrl('reports/q1.pdf', 3600) // 1 hour

Storage RLS policies

sql
-- Users can upload to their own folder
create policy "Users upload own avatar"
  on storage.objects for insert
  to authenticated
  with check (
    bucket_id = 'avatars'
    and (storage.foldername(name))[1] = auth.uid()::text
  );

Performance

Realtime subscriptions hold open WebSocket connections — subscribe only to tables you need and unsubscribe on component unmount. For high-traffic tables, filter events server-side rather than syncing every row to every client.

Back to index


8. React frontend integration

Install SDK & environment variables

bash
npm install @supabase/supabase-js @supabase/ssr
env
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...your-anon-key
# NEVER expose service_role in frontend
SUPABASE_SERVICE_ROLE_KEY=eyJ...server-only

Client initialization (Next.js App Router)

typescript
// lib/supabase/client.ts — browser client
import { createBrowserClient } from '@supabase/ssr'
 
export function createClient() {
  return createBrowserClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!)
}
typescript
// lib/supabase/server.ts — Server Components / Route Handlers
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
 
export async function createClient() {
  const cookieStore = await cookies()
  return createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, {
    cookies: {
      getAll: () => cookieStore.getAll(),
      setAll: (cookiesToSet) => {
        cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options))
      },
    },
  })
}

React hook — auth state

tsx
'use client'
 
import { useEffect, useState } from 'react'
import { createClient } from '@/lib/supabase/client'
import type { User } from '@supabase/supabase-js'
 
export function useUser() {
  const supabase = createClient()
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(true)
 
  useEffect(() => {
    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null)
      setLoading(false)
    })
 
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange((_event, session) => {
      setUser(session?.user ?? null)
    })
 
    return () => subscription.unsubscribe()
  }, [supabase])
 
  return { user, loading }
}

Fetch posts with auth

tsx
'use client'
 
import { useEffect, useState } from 'react'
import { createClient } from '@/lib/supabase/client'
 
type Post = { id: string; title: string; content: string }
 
export function PostList() {
  const supabase = createClient()
  const [posts, setPosts] = useState<Post[]>([])
 
  useEffect(() => {
    supabase
      .from('posts')
      .select('id, title, content')
      .eq('published', true)
      .then(({ data }) => setPosts(data ?? []))
  }, [supabase])
 
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Note

Use @supabase/ssr for Next.js — it handles cookie-based sessions for Server Components and middleware/proxy auth checks. Plain @supabase/supabase-js is fine for Vite/CRA client-only apps.

Back to index


9. Best practices

Tip

Production checklist

  • Enable RLS on every public table — no exceptions
  • Never expose service_role key in frontend — server-only
  • Configure custom SMTP for auth emails before launch
  • Commit supabase/migrations/ to Git; use supabase db push locally
  • Use anon key in client; verify JWT in Server Components for sensitive reads
  • Test RLS policies as different users in SQL Editor
  • Unsubscribe Realtime channels on component unmount
  • Use signed URLs for private Storage buckets
  • Pin @supabase/supabase-js version; review Supabase changelog
  • Generate TypeScript types: supabase gen types typescript --local > types/database.ts

Generate TypeScript types from schema

bash
supabase gen types typescript --project-id your-project-ref > src/types/database.ts
typescript
import { createClient } from '@supabase/supabase-js'
import type { Database } from '@/types/database'
 
const supabase = createClient<Database>(url, anonKey)
 
// posts table is fully typed
const { data } = await supabase.from('posts').select('*')

Environment & Git

gitignore
.env
.env.local
supabase/.temp/

Interview Answer

"How is Supabase different from Firebase?"

Both are BaaS platforms, but Supabase uses PostgreSQL (relational SQL, joins, RLS) while Firebase uses NoSQL (Firestore). Supabase gives you raw SQL access, portable data, and open-source self-hosting. Auth, Realtime, and Storage are comparable — but Supabase security is enforced via Postgres RLS policies, not security rules in a proprietary format.

Back to index


Summary

Supabase delivers Postgres as a service with Auth, Realtime, Storage, and auto-generated APIs in one platform. Start local with the CLI, model data in SQL migrations, lock down access with RLS, and wire React with @supabase/ssr. Every crash-course topic — architecture, hosting, CRUD, auth, security, realtime, storage, and frontend hooks — is covered above.

Related reads: Prisma ORM Deep Dive for schema-first ORM patterns alongside Supabase, and MERN Course: MongoDB Essentials for comparing document vs relational approaches.

Share this article

XLinkedInFacebook
Kazi Rahamatullah

Written by

Kazi Rahamatullah

FullStack Developer

X / TwitterGitHubLinkedIn

Subscribe to my newsletter

Stay up to date and get notified when I share new contents.

No spam ever, unsubscribe anytime