BelajarKoding Logobelajarkoding

Platform belajar web development Indonesia. Artikel, cheat sheets, roadmap, dan code challenges untuk developer Indonesia.

Navigasi

  • Artikel
  • Cheat Sheets
  • Roadmap
  • Challenges
  • Pricing
  • Search

Produk Lain

  • JagoHermes
  • KelasClaude
  • KilatKoding
  • BelajarVibeCoding
  • JualanKoding

Support

  • Privacy Policy
  • Terms of Service
  • Email

© 2026 BelajarKoding. All rights reserved.

Galih PratamaBagian dari ekosistem Galih Pratama
belajarkoding LogobyGalih Pratama
RoadmapArtikelCheat SheetsChallengesUpgrade
belajarkoding LogobyGalih Pratama
RoadmapArtikelCheat SheetsChallengesUpgrade
belajarkoding LogobyGalih Pratama
RoadmapArtikelCheat SheetsChallengesUpgrade

Daftar Isi

Setup & InstallationInstall Supabase ClientInitialize ClientEnvironment VariablesServer-Side Client (Next.js)AuthenticationSign Up (Email/Password)Sign InSign OutGet Current UserListen to Auth ChangesReset PasswordUpdate User MetadataReal-time SubscriptionsSubscribe to Table ChangesSubscribe to Specific RowMultiple Listeners on One ChannelBroadcast (Custom Events)Presence (Track Online Users)StorageUpload FileDownload FileList FilesDelete FilesMove/Copy FilesRow Level Security (RLS)Enable RLSCommon PoliciesBypass RLS (Server-side)Edge FunctionsCreate Edge FunctionBasic Edge FunctionDeploy Edge FunctionInvoke Edge FunctionTypeScript TypesGenerate Types from DatabaseUse Generated TypesDefine Custom TypesCommon PatternsProtected Route (Next.js App Router)Pagination Hook (React)Optimistic UI UpdateDebounced SearchBest PracticesSecurityPerformanceReal-timeDatabaseQuick CommandsUseful Resources
SupabasePostgreSQLBackendBaaSDatabase

Supabase Cheat Sheet

Quick reference Supabase. Setup, authentication, database queries, real-time subscriptions, storage, Row Level Security, dan best practices untuk building modern apps.

JavaScript11 min read2.192 kata
Silakan login atau daftar untuk membaca cheat sheet ini.

#Setup & Installation

#Install Supabase Client

bash
# npm
npm install @supabase/supabase-js
 
# pnpm
pnpm add @supabase/supabase-js
 
# yarn
yarn add @supabase/supabase-js

#Initialize Client

typescript
// lib/supabase.ts
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)

#Environment Variables

bash
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc...
SUPABASE_SERVICE_ROLE_KEY=eyJhbGc... # Server-side only

#Server-Side Client (Next.js)

typescript
// lib/supabase-server.ts
import { createClient } from '@supabase/supabase-js'
 
export const supabaseAdmin = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!, // Bypass RLS
  {
    auth: {
      autoRefreshToken: false,
      persistSession: false
    }
  }
)

#Database Operations

#Select (Read)

typescript
// Get all rows
const { data, error } = await supabase
  .from('posts')
  .select('*')
 
// Select specific columns
const { data } = await supabase
  .from('posts')
  .select('id, title, author')
 
// Filter by condition
const { data } = await supabase
  .from('posts')
  .select('*')
  .eq('status', 'published')
  .gt('views', 100)
 
// Join with other tables
const { data } = await supabase
  .from('posts')
  .select(`
    id,
    title,
    author:users (name, email)
  `)
 
// Pagination
const { data } = await supabase
  .from('posts')
  .select('*')
  .range(0, 9) // First 10 items

#Insert (Create)

typescript
// Insert single row
const { data, error } = await supabase
  .from('posts')
  .insert({ title: 'New Post', content: 'Hello World' })
  .select()
 
// Insert multiple rows
const { data } = await supabase
  .from('posts')
  .insert([
    { title: 'Post 1' },
    { title: 'Post 2' }
  ])
  .select()
 
// Upsert (insert or update if exists)
const { data } = await supabase
  .from('posts')
  .upsert({ id: 1, title: 'Updated' })
  .select()

#Update

typescript
// Update by ID
const { data, error } = await supabase
  .from('posts')
  .update({ title: 'Updated Title' })
  .eq('id', 1)
  .select()
 
// Update with conditions
const { data } = await supabase
  .from('posts')
  .update({ status: 'archived' })
  .lt('created_at', '2024-01-01')

#Delete

typescript
// Delete by ID
const { error } = await supabase
  .from('posts')
  .delete()
  .eq('id', 1)
 
// Delete with conditions
const { error } = await supabase
  .from('posts')
  .delete()
  .eq('status', 'draft')
  .lt('created_at', '2024-01-01')

#Query Filters

typescript
// Comparison operators
.eq('column', value)       // Equal
.neq('column', value)      // Not equal
.gt('column', value)       // Greater than
.gte('column', value)      // Greater than or equal
.lt('column', value)       // Less than
.lte('column', value)      // Less than or equal
 
// Pattern matching
.like('column', '%pattern%')     // Case-sensitive LIKE
.ilike('column', '%pattern%')    // Case-insensitive LIKE
 
// In array
.in('column', [value1, value2])
 
// Null checks
.is('column', null)
.not('column', 'is', null)
 
// Full text search
.textSearch('column', 'keyword')
 
// Order & Limit
.order('created_at', { ascending: false })
.limit(10)

#Authentication

#Sign Up (Email/Password)

typescript
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'password123',
  options: {
    data: {
      full_name: 'John Doe',
      age: 25
    }
  }
})

#Sign In

typescript
// Email & Password
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'password123'
})
 
// Magic Link (Passwordless)
const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://example.com/auth/callback'
  }
})
 
// OAuth (Google, GitHub, etc.)
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://example.com/auth/callback'
  }
})

#Sign Out

typescript
const { error } = await supabase.auth.signOut()

#Get Current User

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

#Listen to Auth Changes

typescript
const { data: { subscription } } = supabase.auth.onAuthStateChange(
  (event, session) => {
    console.log(event, session)
    // events: 'SIGNED_IN', 'SIGNED_OUT', 'TOKEN_REFRESHED', etc.
  }
)
 
// Cleanup
subscription.unsubscribe()

#Reset Password

typescript
// Send reset email
const { data, error } = await supabase.auth.resetPasswordForEmail(
  'user@example.com',
  {
    redirectTo: 'https://example.com/reset-password'
  }
)
 
// Update password (after reset)
const { data, error } = await supabase.auth.updateUser({
  password: 'new_password'
})

#Update User Metadata

typescript
const { data, error } = await supabase.auth.updateUser({
  data: {
    full_name: 'Jane Doe',
    avatar_url: 'https://example.com/avatar.jpg'
  }
})

#Real-time Subscriptions

#Subscribe to Table Changes

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

#Subscribe to Specific Row

typescript
const subscription = supabase
  .channel('post-1')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'posts',
      filter: 'id=eq.1'
    },
    (payload) => {
      console.log('Post 1 updated!', payload)
    }
  )
  .subscribe()

#Multiple Listeners on One Channel

typescript
const channel = supabase.channel('room-1')
 
// Listen to inserts
channel.on(
  'postgres_changes',
  { event: 'INSERT', schema: 'public', table: 'messages' },
  (payload) => console.log('New message', payload)
)
 
// Listen to presence
channel.on('presence', { event: 'sync' }, () => {
  const state = channel.presenceState()
  console.log('Online users:', state)
})
 
channel.subscribe()

#Broadcast (Custom Events)

typescript
// Send message
await channel.send({
  type: 'broadcast',
  event: 'cursor-pos',
  payload: { x: 100, y: 200 }
})
 
// Listen to broadcast
channel.on('broadcast', { event: 'cursor-pos' }, (payload) => {
  console.log('Cursor moved:', payload)
})

#Presence (Track Online Users)

typescript
const channel = supabase.channel('room-1')
 
// Track user presence
await channel.subscribe(async (status) => {
  if (status === 'SUBSCRIBED') {
    await channel.track({
      user_id: 'user-1',
      online_at: new Date().toISOString()
    })
  }
})
 
// Listen to presence changes
channel.on('presence', { event: 'sync' }, () => {
  const state = channel.presenceState()
  console.log('Online:', state)
})
 
channel.on('presence', { event: 'join' }, ({ key, newPresences }) => {
  console.log('User joined:', newPresences)
})
 
channel.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
  console.log('User left:', leftPresences)
})

#Storage

#Upload File

typescript
// Upload file
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('user-1.png', file, {
    cacheControl: '3600',
    upsert: true
  })
 
// Upload with path
const { data, error } = await supabase.storage
  .from('documents')
  .upload('public/doc.pdf', file)

#Download File

typescript
// Download file
const { data, error } = await supabase.storage
  .from('avatars')
  .download('user-1.png')
 
// Get public URL
const { data } = supabase.storage
  .from('avatars')
  .getPublicUrl('user-1.png')
 
console.log(data.publicUrl)
 
// Create signed URL (private buckets)
const { data, error } = await supabase.storage
  .from('private')
  .createSignedUrl('secret.pdf', 60) // Valid for 60 seconds

#List Files

typescript
const { data, error } = await supabase.storage
  .from('avatars')
  .list('public', {
    limit: 100,
    offset: 0,
    sortBy: { column: 'name', order: 'asc' }
  })

#Delete Files

typescript
// Delete single file
const { data, error } = await supabase.storage
  .from('avatars')
  .remove(['user-1.png'])
 
// Delete multiple files
const { data, error } = await supabase.storage
  .from('avatars')
  .remove(['user-1.png', 'user-2.png'])

#Move/Copy Files

typescript
// Move file
const { data, error } = await supabase.storage
  .from('avatars')
  .move('old-path.png', 'new-path.png')
 
// Copy file
const { data, error } = await supabase.storage
  .from('avatars')
  .copy('source.png', 'destination.png')

#Row Level Security (RLS)

#Enable RLS

sql
-- Enable RLS on table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

#Common Policies

sql
-- Allow anyone to read
CREATE POLICY "Public posts are viewable by everyone"
  ON posts FOR SELECT
  USING (true);
 
-- Users can only see their own data
CREATE POLICY "Users can view own posts"
  ON posts FOR SELECT
  USING (auth.uid() = user_id);
 
-- Users can insert their own data
CREATE POLICY "Users can insert own posts"
  ON posts FOR INSERT
  WITH CHECK (auth.uid() = user_id);
 
-- Users can update their own data
CREATE POLICY "Users can update own posts"
  ON posts FOR UPDATE
  USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);
 
-- Users can delete their own data
CREATE POLICY "Users can delete own posts"
  ON posts FOR DELETE
  USING (auth.uid() = user_id);
 
-- Check user role
CREATE POLICY "Only admins can delete"
  ON posts FOR DELETE
  USING (
    auth.uid() IN (
      SELECT id FROM users WHERE role = 'admin'
    )
  );

#Bypass RLS (Server-side)

typescript
// Use service role key to bypass RLS
import { createClient } from '@supabase/supabase-js'
 
const supabaseAdmin = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
)
 
// Now queries bypass RLS
const { data } = await supabaseAdmin
  .from('posts')
  .select('*') // Returns all rows regardless of RLS

#Edge Functions

#Create Edge Function

bash
# Using Supabase CLI
supabase functions new my-function

#Basic Edge Function

typescript
// supabase/functions/my-function/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) => {
  try {
    // Get data from request
    const { name } = await req.json()
 
    // Initialize Supabase client
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_ANON_KEY') ?? ''
    )
 
    // Query database
    const { data, error } = await supabase
      .from('users')
      .select('*')
      .eq('name', name)
 
    if (error) throw error
 
    return new Response(
      JSON.stringify({ data }),
      { headers: { 'Content-Type': 'application/json' } }
    )
  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      { status: 400, headers: { 'Content-Type': 'application/json' } }
    )
  }
})

#Deploy Edge Function

bash
supabase functions deploy my-function

#Invoke Edge Function

typescript
// From client
const { data, error } = await supabase.functions.invoke('my-function', {
  body: { name: 'John' }
})
 
// With custom headers
const { data, error } = await supabase.functions.invoke('my-function', {
  body: { name: 'John' },
  headers: {
    'Authorization': `Bearer ${token}`
  }
})

#TypeScript Types

#Generate Types from Database

bash
# Install Supabase CLI
npm install supabase --save-dev
 
# Login
npx supabase login
 
# Generate types
npx supabase gen types typescript --project-id YOUR_PROJECT_ID > types/database.ts

#Use Generated Types

typescript
// types/database.ts is auto-generated
import { Database } from '@/types/database'
 
// Create typed client
export const supabase = createClient<Database>(
  supabaseUrl,
  supabaseAnonKey
)
 
// Now queries are fully typed!
const { data } = await supabase
  .from('posts') // Autocomplete available
  .select('*')
 
// data is typed as Post[]

#Define Custom Types

typescript
import { Database } from '@/types/database'
 
type Post = Database['public']['Tables']['posts']['Row']
type PostInsert = Database['public']['Tables']['posts']['Insert']
type PostUpdate = Database['public']['Tables']['posts']['Update']
 
// Use in components
function PostList({ posts }: { posts: Post[] }) {
  // ...
}

#Common Patterns

#Protected Route (Next.js App Router)

typescript
// app/dashboard/page.tsx
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
 
export default async function Dashboard() {
  const supabase = createServerComponentClient({ cookies })
 
  const { data: { session } } = await supabase.auth.getSession()
 
  if (!session) {
    redirect('/login')
  }
 
  return <div>Protected content</div>
}

#Pagination Hook (React)

typescript
import { useState, useEffect } from 'react'
 
function usePagination(table: string, pageSize = 10) {
  const [data, setData] = useState([])
  const [page, setPage] = useState(0)
  const [loading, setLoading] = useState(false)
 
  useEffect(() => {
    async function fetchData() {
      setLoading(true)
      const { data } = await supabase
        .from(table)
        .select('*')
        .range(page * pageSize, (page + 1) * pageSize - 1)
 
      setData(data || [])
      setLoading(false)
    }
 
    fetchData()
  }, [page, table])
 
  return { data, loading, page, setPage }
}

#Optimistic UI Update

typescript
async function toggleTodo(id: string, isComplete: boolean) {
  // Update UI immediately
  setTodos(prev =>
    prev.map(t => t.id === id ? { ...t, is_complete: !isComplete } : t)
  )
 
  // Update database
  const { error } = await supabase
    .from('todos')
    .update({ is_complete: !isComplete })
    .eq('id', id)
 
  // Revert on error
  if (error) {
    setTodos(prev =>
      prev.map(t => t.id === id ? { ...t, is_complete } : t)
    )
    toast.error('Failed to update')
  }
}

#Debounced Search

typescript
import { useState, useEffect } from 'react'
import { useDebounce } from 'use-debounce'
 
function SearchPosts() {
  const [query, setQuery] = useState('')
  const [debouncedQuery] = useDebounce(query, 500)
  const [results, setResults] = useState([])
 
  useEffect(() => {
    if (!debouncedQuery) {
      setResults([])
      return
    }
 
    async function search() {
      const { data } = await supabase
        .from('posts')
        .select('*')
        .ilike('title', `%${debouncedQuery}%`)
 
      setResults(data || [])
    }
 
    search()
  }, [debouncedQuery])
 
  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      {results.map(post => <div key={post.id}>{post.title}</div>)}
    </>
  )
}

#Best Practices

#Security

  • ✅ Always enable Row Level Security (RLS) on tables
  • ✅ Use service role key only on server-side
  • ✅ Never expose service role key to client
  • ✅ Validate user input before queries
  • ✅ Use policies to restrict data access

#Performance

  • ✅ Use indexes for frequently queried columns
  • ✅ Select only columns you need (select('id, title'))
  • ✅ Implement pagination for large datasets
  • ✅ Use .single() when fetching one row
  • ✅ Enable PostgREST cache headers

#Real-time

  • ✅ Unsubscribe from channels when component unmounts
  • ✅ Use filters to reduce unnecessary events
  • ✅ Implement reconnection logic for unstable networks
  • ✅ Use presence for tracking online users efficiently

#Database

  • ✅ Use transactions for related operations
  • ✅ Add constraints (UNIQUE, NOT NULL, CHECK)
  • ✅ Create proper foreign key relationships
  • ✅ Use UUID for primary keys
  • ✅ Add created_at and updated_at timestamps

#Quick Commands

bash
# Supabase CLI
supabase init              # Initialize Supabase project
supabase start             # Start local Supabase
supabase stop              # Stop local Supabase
supabase db reset          # Reset local database
supabase db push           # Push migrations to remote
supabase gen types typescript  # Generate types
supabase functions new     # Create new Edge Function
supabase functions deploy  # Deploy Edge Function

#Useful Resources

  • Docs: supabase.com/docs
  • GitHub: github.com/supabase/supabase
  • Examples: github.com/supabase/supabase/tree/master/examples
  • Discord: discord.supabase.com

Baca Cheat Sheet Lengkap

Login atau daftar akun gratis untuk membaca cheat sheet ini.

LoginDaftar Gratis
Share: