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.

Baca Cheat Sheet Lengkap

Login atau daftar akun gratis untuk membaca cheat sheet ini.

LoginDaftar Gratis
Share:

#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: 




#Database Operations

#Select (Read)

typescript
// Get all rows
const { data, error } = await supabase
  .from('posts')
  .select('*')
 
// Select specific columns
























#Insert (Create)

typescript
// Insert single row
const { data, error } = await supabase
  .from('posts')
  .insert({ title: 'New Post', content: 'Hello World' })
  .select














#Update

typescript
// Update by ID
const { data, error } = await supabase
  .from('posts')
  .update({ title: 'Updated Title' })
  .eq('id', 1)






#Delete

typescript
// Delete by ID
const { error } = await supabase
  .from('posts')
  .delete()
  .eq('id', 1)
 
// Delete with conditions
const { 



#Query Filters

typescript
// Comparison operators
.eq('column', value)       // Equal
.neq('column', value)      // Not equal
.gt('column', value)       // Greater than
.gte




















#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 { 












#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.

#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

#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',









#Subscribe to Specific Row

typescript
const subscription = supabase
  .channel('post-1')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'posts',
      filter: 





#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) 








#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' }, (

#Presence (Track Online Users)

typescript
const channel = supabase.channel('room-1')
 
// Track user presence
await channel.subscribe(async (status) => {
  if (status 




















#Storage

#Upload File

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




#Download File

typescript
// Download file
const { data, error } = await supabase.storage
  .from('avatars')
  .download('user-1.png')
 
// Get public URL
const { data








#List Files

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

#Delete Files

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

#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, 


#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

























#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


#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) =>




























#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 } 





#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



#Define Custom Types

typescript
import { Database } from '@/types/database'
 
type Post = Database['public']['Tables']['posts']['Row']
type PostInsert = Database['public']['Tables'][






#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








#Pagination Hook (React)

typescript
import { useState, useEffect } from 'react'
 
function usePagination(table: string, pageSize = 10) {
  const [data, setData] = useState



















#Optimistic UI Update

typescript
async function toggleTodo(id: string, isComplete: boolean) {
  // Update UI immediately
  setTodos(prev =>
    prev.map(t => t.id ===















#Debounced Search

typescript
import { useState, useEffect } from 'react'
import { useDebounce } from 'use-debounce'
 
function SearchPosts() {
  const [query, setQuery] = useState




























#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


#Useful Resources

  • Docs: supabase.com/docs
  • GitHub: github.com/supabase/supabase
  • Examples: github.com/supabase/supabase/tree/master/examples
  • Discord: discord.supabase.com
false
,
persistSession: false
}
}
)
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 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()
.select()
// Update with conditions
const { data } = await supabase
.from('posts')
.update({ status: 'archived' })
.lt('created_at', '2024-01-01')
error
}
=
await
supabase
.from('posts')
.delete()
.eq('status', 'draft')
.lt('created_at', '2024-01-01')
(
'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)
})
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'
}
})
unsubscribe
()
,
error
}
=
await
supabase.auth.
updateUser
({
password: 'new_password'
})
table: 'posts'
},
(payload) => {
console.log('Change received!', payload)
}
)
.subscribe()
// Cleanup
subscription.unsubscribe()
'id=eq.1'
},
(payload) => {
console.log('Post 1 updated!', payload)
}
)
.subscribe()
=>
console.
log
(
'New message'
, payload)
)
// Listen to presence
channel.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
console.log('Online users:', state)
})
channel.subscribe()
payload
)
=>
{
console.log('Cursor moved:', payload)
})
===
'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)
})
// Upload with path
const { data, error } = await supabase.storage
.from('documents')
.upload('public/doc.pdf', file)
}
=
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
'asc'
}
})
}
=
await
supabase.storage
.from('avatars')
.remove(['user-1.png', 'user-2.png'])
error
}
=
await
supabase.storage
.from('avatars')
.copy('source.png', 'destination.png')
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'
)
);
}
=
await
supabaseAdmin
.from('posts')
.select('*') // Returns all rows regardless of RLS
{
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' } }
)
}
})
=
await
supabase.functions.
invoke
(
'my-function'
, {
body: { name: 'John' },
headers: {
'Authorization': `Bearer ${token}`
}
})
}
=
await
supabase
.from('posts') // Autocomplete available
.select('*')
// data is typed as Post[]
'posts'
][
'Insert'
]
type PostUpdate = Database['public']['Tables']['posts']['Update']
// Use in components
function PostList({ posts }: { posts: Post[] }) {
// ...
}
=
createServerComponentClient
({ cookies })
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
redirect('/login')
}
return <div>Protected content</div>
}
([])
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 }
}
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')
}
}
(
''
)
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>)}
</>
)
}
typescript
# Generate types
supabase functions new # Create new Edge Function
supabase functions deploy # Deploy Edge Function