belajarkoding Platform belajar web development Indonesia. Artikel, cheat sheets, roadmap, dan code challenges untuk developer Indonesia.
Supabase Cheat Sheet Quick reference Supabase. Setup, authentication, database queries, real-time subscriptions, storage, Row Level Security, dan best practices untuk building modern apps.
JavaScript 11 min read 2.192 kataSilakan
login atau
daftar untuk membaca cheat sheet ini.
Baca Cheat Sheet Lengkap Login atau daftar akun gratis untuk membaca cheat sheet ini.
# Setup & Installation
# Install Supabase Client
# npm
npm install @supabase/supabase-js
# pnpm
pnpm add @supabase/supabase-js
# yarn
yarn add @supabase/supabase-js
# Initialize Client
// 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
# .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)
// 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)
// Get all rows
const { data , error } = await supabase
. from ( 'posts' )
. select ( '*' )
// Select specific columns
# Insert (Create)
// Insert single row
const { data , error } = await supabase
. from ( 'posts' )
. insert ({ title: 'New Post' , content: 'Hello World' })
. select
# Update
// Update by ID
const { data , error } = await supabase
. from ( 'posts' )
. update ({ title: 'Updated Title' })
. eq ( 'id' , 1 )
# Delete
// Delete by ID
const { error } = await supabase
. from ( 'posts' )
. delete ()
. eq ( 'id' , 1 )
// Delete with conditions
const {
# Query Filters
// Comparison operators
. eq ( 'column' , value) // Equal
. neq ( 'column' , value) // Not equal
. gt ( 'column' , value) // Greater than
. gte
# Authentication
# Sign Up (Email/Password)
const { data , error } = await supabase.auth. signUp ({
email: 'user@example.com' ,
password: 'password123' ,
options: {
data: {
full_name: 'John Doe' ,
age: 25
}
}
# Sign In
// Email & Password
const { data , error } = await supabase.auth. signInWithPassword ({
email: 'user@example.com' ,
password: 'password123'
})
// Magic Link (Passwordless)
const {
# Sign Out
const { error } = await supabase.auth. signOut ()
# Get Current User
// Get user from session
const { data : { user } } = await supabase.auth. getUser ()
// Get session
const { data : { session } } = await supabase.auth. getSession ()
# Listen to Auth Changes
const { data : { subscription } } = supabase.auth. onAuthStateChange (
( event , session ) => {
console. log (event, session)
// events: 'SIGNED_IN', 'SIGNED_OUT', 'TOKEN_REFRESHED', etc.
}
)
// Cleanup
subscription.
# Reset Password
// 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
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
// Subscribe to all changes
const subscription = supabase
. channel ( 'posts-channel' )
. on (
'postgres_changes' ,
{
event: '*' , // '*', 'INSERT', 'UPDATE', 'DELETE'
schema: 'public' ,
# Subscribe to Specific Row
const subscription = supabase
. channel ( 'post-1' )
. on (
'postgres_changes' ,
{
event: 'UPDATE' ,
schema: 'public' ,
table: 'posts' ,
filter:
# Multiple Listeners on One Channel
const channel = supabase. channel ( 'room-1' )
// Listen to inserts
channel. on (
'postgres_changes' ,
{ event: 'INSERT' , schema: 'public' , table: 'messages' },
( payload )
# Broadcast (Custom Events)
// 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)
const channel = supabase. channel ( 'room-1' )
// Track user presence
await channel. subscribe ( async ( status ) => {
if (status
# Storage
# Upload File
// Upload file
const { data , error } = await supabase.storage
. from ( 'avatars' )
. upload ( 'user-1.png' , file, {
cacheControl: '3600' ,
upsert: true
})
# Download File
// Download file
const { data , error } = await supabase.storage
. from ( 'avatars' )
. download ( 'user-1.png' )
// Get public URL
const { data
# List Files
const { data , error } = await supabase.storage
. from ( 'avatars' )
. list ( 'public' , {
limit: 100 ,
offset: 0 ,
sortBy: { column: 'name' , order:
# Delete Files
// Delete single file
const { data , error } = await supabase.storage
. from ( 'avatars' )
. remove ([ 'user-1.png' ])
// Delete multiple files
const { data , error
# Move/Copy Files
// 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
-- Enable RLS on table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY ;
# Common Policies
-- 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)
// 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
# Using Supabase CLI
supabase functions new my-function
# Basic Edge Function
// 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
supabase functions deploy my-function
# Invoke Edge Function
// 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
# 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
// 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
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)
// 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
import { useState, useEffect } from 'react'
function usePagination ( table : string , pageSize = 10 ) {
const [ data , setData ] = useState
# Optimistic UI Update
async function toggleTodo ( id : string , isComplete : boolean ) {
// Update UI immediately
setTodos ( prev =>
prev. map ( t => t.id ===
# Debounced Search
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
# 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
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