1. Pengenalan Supabase
Supabase adalah platform Backend-as-a-Service (BaaS) open source yang menyediakan semua tools yang dibutuhkan developer untuk membangun aplikasi tanpa perlu membangun backend dari nol. Didirikan pada tahun 2020 oleh Paul Copplestone dan Ant Wilson, Supabase sering disebut sebagai "open source Firebase alternative" karena menawarkan fitur serupa namun dengan fondasi database relational PostgreSQL.
Berbeda dengan Firebase yang menggunakan NoSQL (Firestore), Supabase membangun seluruh infrastrukturnya di atas PostgreSQL β database relational paling powerful dan mature di dunia. Ini berarti developer mendapatkan kekuatan full SQL, foreign keys, joins, dan fitur PostgreSQL lainnya yang tidak tersedia di platform NoSQL.
Apa Saja yang Disuplai Supabase?
| Layanan | Deskripsi | Teknologi Dasar |
|---|---|---|
| Database | PostgreSQL penuh dengan REST API otomatis | PostgreSQL + PostgREST |
| Auth | Sistem autentikasi lengkap (email, OAuth, phone) | GoTrue |
| Storage | Upload dan serve file (gambar, video, dokumen) | S3-compatible |
| Realtime | Subscribe perubahan data secara live | PostgreSQL WAL + WebSocket |
| Edge Functions | Serverless functions di edge (Deno runtime) | Deno + TypeScript |
| Vector | Embedding dan similarity search untuk AI | pgvector |
| RLS | Row Level Security untuk kontrol akses data | PostgreSQL RLS |
Supabase vs Firebase
| Aspek | Supabase | Firebase |
|---|---|---|
| Database | PostgreSQL (Relational / SQL) | Firestore (NoSQL / Document) |
| Open Source | β Ya | β Proprietary |
| Self-host | β Bisa (Docker) | β Tidak bisa |
| Query Language | SQL penuh + REST API | NoSQL query (terbatas) |
| Relasi Data | β Foreign keys, joins | β οΈ Manual denormalization |
| Realtime | β Berdasarkan perubahan tabel | β Berdasarkan dokumen/collection |
| Vendor Lock-in | π’ Rendah (standard SQL) | π΄ Tinggi (proprietary) |
| Harga Gratis | 500 MB DB, 1 GB storage, 50K user | 1 GB storage, 50K reads/hari |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β SUPABASE PLATFORM β β β β ββββββββββββ ββββββββββββ ββββββββββββ βββββββββββββββββ β β β Supabase β β Supabase β β Supabase β β Edge β β β β Auth β β DB β β Storage β β Functions β β β β (GoTrue) β β(PostgREST)β β β β (Deno) β β β βββββββ¬βββββ βββββββ¬βββββ ββββββ¬ββββββ ββββββββ¬βββββββββ β β β β β β β β βββββββββββββββ΄βββββββββββββ΄βββββββββββββββββ β β β β β βββββββΌβββββββ β β β PostgreSQL β β Database utama β β β + pgvectorβ β AI embeddings β β β + RLS β β Row Level Security β β ββββββββββββββ β β β β β βββββββΌβββββββ β β β Realtime β β WebSocket subscriptions β β β Engine β β β ββββββββββββββ β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Memulai Proyek Supabase
Untuk mulai menggunakan Supabase, ikuti langkah berikut:
- Buka supabase.com dan daftar akun gratis
- Klik "New Project" dan isi nama project, password database, dan pilih region
- Tunggu sekitar 2 menit hingga project selesai di-provision
- Anda akan mendapatkan Project URL dan Anon Key di menu Settings β API
// Instalasi Supabase JS client
// npm install @supabase/supabase-js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://xxxxx.supabase.co'
const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6...'
const supabase = createClient(supabaseUrl, supabaseAnonKey)
// Sekarang Anda bisa menggunakan 'supabase' di seluruh aplikasi
console.log('Supabase client berhasil diinisialisasi!')
Jangan pernah menyimpan Service Role Key di frontend! Gunakan hanya Anon Key untuk operasi dari sisi client. Service Role Key memiliki akses penuh dan harus digunakan hanya di server/backend.
2. Autentikasi (Supabase Auth)
Supabase Auth adalah sistem autentikasi built-in yang mendukung berbagai metode login. Dengan hanya beberapa baris kode, Anda sudah bisa menambahkan fitur registrasi, login, reset password, dan OAuth social login ke aplikasi Anda.
Metode Autentikasi yang Didukung
| Metode | Deskripsi | Kompleksitas |
|---|---|---|
| Email & Password | Registrasi dan login dengan email serta password | β Mudah |
| Magic Link | Login via link yang dikirim ke email (tanpa password) | β Mudah |
| OAuth (Google, GitHub, dll.) | Login dengan akun social media | ββ Sedang |
| Phone OTP | Login dengan kode verifikasi SMS | ββ Sedang |
| Anonymous Login | Login tanpa identitas (guest mode) | β Mudah |
| SSO / SAML | Enterprise single sign-on | βββ Lanjutan |
Registrasi dengan Email & Password
// Registrasi pengguna baru
async function signUp(email, password) {
const { data, error } = await supabase.auth.signUp({
email: email,
password: password,
})
if (error) {
console.error('Error registrasi:', error.message)
return { success: false, error: error.message }
}
console.log('Registrasi berhasil!', data.user)
// Pengguna akan menerima email verifikasi
return { success: true, user: data.user }
}
// Contoh penggunaan:
await signUp('user@example.com', 'password123!')
Login dengan Email & Password
// Login pengguna yang sudah terdaftar
async function signIn(email, password) {
const { data, error } = await supabase.auth.signInWithPassword({
email: email,
password: password,
})
if (error) {
console.error('Error login:', error.message)
return { success: false, error: error.message }
}
console.log('Login berhasil!', data.session)
// Session token disimpan otomatis oleh Supabase client
return { success: true, session: data.session }
}
OAuth Social Login (Google)
// Login dengan Google OAuth
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'https://yourapp.com/callback',
queryParams: {
access_type: 'offline',
prompt: 'consent',
}
}
})
if (error) {
console.error('Error OAuth:', error.message)
}
// User akan di-redirect ke Google, lalu kembali ke redirectTo URL
}
// Provider lain yang didukung: github, gitlab, bitbucket,
// discord, twitter, facebook, apple, slack, spotify, dll.
Menangani Session & Auth State
// Mendapatkan session saat ini
const { data: { session } } = await supabase.auth.getSession()
if (session) {
console.log('User sudah login:', session.user.email)
} else {
console.log('User belum login')
}
// Mendapatkan user saat ini
const { data: { user } } = await supabase.auth.getUser()
console.log('User ID:', user?.id)
// Mendengarkan perubahan auth state (login/logout)
supabase.auth.onAuthStateChange((event, session) => {
console.log('Auth event:', event)
// Event: SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED,
// USER_UPDATED, PASSWORD_RECOVERY
if (event === 'SIGNED_IN') {
console.log('User login:', session.user.email)
} else if (event === 'SIGNED_OUT') {
console.log('User logout')
}
})
// Logout
async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) {
console.error('Error logout:', error.message)
} else {
console.log('Berhasil logout')
}
}
Supabase Auth secara otomatis menyimpan session di localStorage browser. Ketika user refresh halaman, session akan dipulihkan otomatis. Tidak perlu mengelola JWT token secara manual!
3. Database PostgreSQL
Inti dari Supabase adalah PostgreSQL β database relational yang powerful dan sudah digunakan oleh jutaan aplikasi di seluruh dunia. Setiap project Supabase mendapatkan database PostgreSQL penuh yang bisa diakses melalui SQL editor langsung di dashboard atau melalui client library.
Membuat Tabel
Anda bisa membuat tabel melalui SQL Editor di dashboard Supabase atau melalui migrations:
-- Membuat tabel profiles (terhubung dengan auth.users)
CREATE TABLE 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 DEFAULT '',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Membuat tabel posts
CREATE TABLE posts (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
author_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
published BOOLEAN DEFAULT false,
tags TEXT[] DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Membuat tabel comments
CREATE TABLE comments (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
post_id BIGINT REFERENCES posts(id) ON DELETE CASCADE NOT NULL,
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index untuk performa query
CREATE INDEX idx_posts_author ON posts(author_id);
CREATE INDEX idx_posts_published ON posts(published);
CREATE INDEX idx_comments_post ON comments(post_id);
CRUD Operations dengan Client Library
// ===== CREATE: Menambahkan data baru =====
const { data: newPost, error: createError } = await supabase
.from('posts')
.insert({
title: 'Belajar Supabase',
content: 'Supabase adalah platform BaaS yang powerful...',
author_id: user.id,
published: true,
tags: ['supabase', 'tutorial', 'database']
})
.select() // Mengembalikan data yang baru di-insert
.single() // Hanya satu row
console.log('Post baru:', newPost)
// ===== READ: Mengambil data =====
// Ambil semua post yang dipublish
const { data: posts, error: readError } = await supabase
.from('posts')
.select(`
id,
title,
content,
created_at,
profiles (username, avatar_url),
comments (count)
`)
.eq('published', true) // WHERE published = true
.order('created_at', { ascending: false }) // ORDER BY DESC
.limit(10) // LIMIT 10
console.log('Daftar posts:', posts)
// ===== UPDATE: Mengubah data =====
const { data: updatedPost, error: updateError } = await supabase
.from('posts')
.update({
title: 'Tutorial Supabase Lengkap',
updated_at: new Date().toISOString()
})
.eq('id', 1) // WHERE id = 1
.select()
.single()
// ===== DELETE: Menghapus data =====
const { error: deleteError } = await supabase
.from('posts')
.delete()
.eq('id', 1) // WHERE id = 1
Query Lanjutan (Filter & Join)
// Filter dengan multiple kondisi
const { data } = await supabase
.from('posts')
.select('*')
.eq('published', true) // equals
.ilike('title', '%supabase%') // LIKE (case insensitive)
.contains('tags', ['tutorial']) // array contains
.gte('created_at', '2026-01-01') // >= tanggal
.order('created_at', { ascending: false })
.range(0, 9) // Pagination: row 0-9
// Full-text search
const { data: searchResults } = await supabase
.from('posts')
.select('*')
.textSearch('content', 'belajar & supabase', {
type: 'websearch',
config: 'english'
})
// Aggregasi: count semua post per author
const { data: stats } = await supabase
.from('posts')
.select('author_id, count()', { count: 'exact' })
.eq('published', true)
.group('author_id')
Database Functions & RPC
-- Membuat fungsi database untuk search dengan ranking
CREATE OR REPLACE FUNCTION search_posts(search_term TEXT)
RETURNS TABLE (
id BIGINT,
title TEXT,
content TEXT,
relevance REAL
) AS $$
BEGIN
RETURN QUERY
SELECT
p.id,
p.title,
p.content,
ts_rank(
to_tsvector('english', p.title || ' ' || COALESCE(p.content, '')),
plainto_tsquery('english', search_term)
) AS relevance
FROM posts p
WHERE p.published = true
AND to_tsvector('english', p.title || ' ' || COALESCE(p.content, ''))
@@ plainto_tsquery('english', search_term)
ORDER BY relevance DESC;
END;
$$ LANGUAGE plpgsql;
// Memanggil database function dari client
const { data: results, error } = await supabase
.rpc('search_posts', { search_term: 'database tutorial' })
console.log('Hasil pencarian:', results)
4. Supabase Storage
Supabase Storage memungkinkan Anda meng-upload, menyimpan, dan melayani file seperti gambar, video, dokumen, dan aset lainnya. Storage mendukung transformasi gambar secara on-the-fly (resize, crop, format conversion) dan memiliki sistem bucket untuk pengelompokan file.
Konsep Storage
| Konsep | Penjelasan |
|---|---|
| Bucket | Container untuk file β mirip folder di S3. Bisa public atau private |
| Object | File individual di dalam bucket |
| Path | Lokasi file di dalam bucket (bisa nested, misal: avatars/user1/profile.jpg) |
| Public Bucket | File bisa diakses tanpa autentikasi |
| Private Bucket | File hanya bisa diakses oleh user yang berhak (perlu RLS) |
| Transform | Resize, crop, dan ubah format gambar via URL parameter |
Membuat Bucket
// Membuat bucket baru (biasanya dilakukan sekali di dashboard)
const { data: bucket, error } = await supabase
.storage
.createBucket('avatars', {
public: true, // File bisa diakses publik
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
fileSizeLimit: 5 * 1024 * 1024, // Max 5 MB
})
if (error) console.error('Gagal membuat bucket:', error.message)
Upload & Download File
// Upload file
async function uploadAvatar(userId, file) {
const filePath = `${userId}/avatar.${file.name.split('.').pop()}`
const { data, error } = await supabase
.storage
.from('avatars')
.upload(filePath, file, {
cacheControl: '3600', // Cache 1 jam
upsert: true, // Timpa jika sudah ada
})
if (error) {
console.error('Upload gagal:', error.message)
return null
}
console.log('Upload berhasil:', data.path)
return data.path
}
// Mendapatkan URL publik
function getAvatarUrl(filePath) {
const { data } = supabase
.storage
.from('avatars')
.getPublicUrl(filePath)
return data.publicUrl
// Contoh: https://xxxxx.supabase.co/storage/v1/object/public/avatars/user123/avatar.jpg
}
// Download file
async function downloadFile(filePath) {
const { data, error } = await supabase
.storage
.from('avatars')
.download(filePath)
if (error) {
console.error('Download gagal:', error.message)
return null
}
// data adalah Blob object
const url = URL.createObjectURL(data)
const img = document.createElement('img')
img.src = url
document.body.appendChild(img)
}
// Hapus file
async function deleteFile(filePath) {
const { error } = await supabase
.storage
.from('avatars')
.remove([filePath])
if (error) console.error('Hapus gagal:', error.message)
}
Transformasi Gambar
// Resize dan transform gambar via URL
function getResizedAvatar(filePath, width = 200, height = 200) {
const { data } = supabase
.storage
.from('avatars')
.getPublicUrl(filePath, {
transform: {
width: width,
height: height,
resize: 'cover', // 'cover', 'contain', atau 'fill'
format: 'webp', // Konversi ke WebP (lebih kecil)
quality: 80, // Kualitas kompresi (0-100)
}
})
return data.publicUrl
// URL akan mengandung transform parameter:
// .../render/image/public/avatars/user123/avatar.jpg?width=200&height=200&resize=cover&format=webp&quality=80
}
5. Fitur Realtime
Supabase Realtime memungkinkan Anda mendengarkan perubahan data di database secara langsung melalui WebSocket. Ketika data berubah (insert, update, delete), semua client yang subscribe akan menerima notifikasi secara instan β tanpa polling!
Tiga Jenis Realtime
| Jenis | Deskripsi | Cocok untuk |
|---|---|---|
| Postgres Changes | Subscribe perubahan pada tabel (INSERT, UPDATE, DELETE) | Dashboard live, notifikasi data |
| Broadcast | Kirim pesan antar client (tidak tersimpan di DB) | Chat, cursor sharing, game state |
| Presence | Track status online/offline user | Online indicators, collaborative editing |
Subscribe Perubahan Tabel
// Subscribe ke semua perubahan pada tabel 'posts'
const channel = supabase
.channel('posts-changes')
.on(
'postgres_changes',
{
event: '*', // INSERT, UPDATE, DELETE, atau '*'
schema: 'public',
table: 'posts',
// filter: 'author_id=eq.user-id-123' // Filter opsional
},
(payload) => {
console.log('Perubahan terdeteksi:', payload.eventType)
console.log('Data baru:', payload.new)
console.log('Data lama:', payload.old)
// Update UI berdasarkan jenis event
switch (payload.eventType) {
case 'INSERT':
addPostToUI(payload.new)
showNotification('Post baru ditambahkan!')
break
case 'UPDATE':
updatePostInUI(payload.new)
break
case 'DELETE':
removePostFromUI(payload.old.id)
break
}
}
)
.subscribe((status) => {
console.log('Status subscription:', status)
// Status: SUBSCRIBED, CLOSED, CHANNEL_ERROR
})
// Unsubscribe ketika tidak diperlukan lagi
// supabase.removeChannel(channel)
Broadcast & Presence
// Broadcast: Kirim pesan ke semua client yang subscribe
const chatChannel = supabase.channel('room-1')
// Dengarkan broadcast dari orang lain
chatChannel
.on('broadcast', { event: 'chat-message' }, (payload) => {
console.log('Pesan dari:', payload.payload.user)
console.log('Isi pesan:', payload.payload.message)
addMessageToUI(payload.payload)
})
.subscribe()
// Kirim pesan broadcast
async function sendMessage(message) {
await chatChannel.send({
type: 'broadcast',
event: 'chat-message',
payload: {
user: currentUser.username,
message: message,
timestamp: new Date().toISOString()
}
})
}
// Presence: Track siapa yang sedang online
const presenceChannel = supabase.channel('online-users')
presenceChannel
.on('presence', { event: 'sync' }, () => {
const state = presenceChannel.presenceState()
console.log('Online users:', Object.keys(state).length)
updateOnlineUsersList(state)
})
.on('presence', { event: 'join' }, ({ key, newPresences }) => {
console.log('User bergabung:', newPresences)
})
.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
console.log('User keluar:', leftPresences)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await presenceChannel.track({
user_id: currentUser.id,
username: currentUser.username,
online_at: new Date().toISOString()
})
}
})
Untuk menggunakan Realtime Postgres Changes, Anda harus mengaktifkan Realtime pada tabel yang ingin di-subscribe. Buka dashboard Supabase β Database β Replication β aktifkan untuk tabel yang diinginkan. Tanpa ini, subscription tidak akan menerima perubahan.
6. Edge Functions
Supabase Edge Functions adalah serverless functions yang berjalan di edge β dekat dengan user β menggunakan Deno runtime. Edge Functions cocok untuk logika backend kustom yang tidak bisa dilakukan langsung dari client, seperti payment processing, webhook handling, email sending, atau operasi yang memerlukan secret keys.
Edge Functions vs Database Functions
| Aspek | Edge Functions | Database Functions (RPC) |
|---|---|---|
| Runtime | Deno (TypeScript) | PL/pgSQL (di dalam PostgreSQL) |
| Lingkup | Bisa akses internet, API eksternal | Hanya database internal |
| Lokasi | Edge (dekat user) | Database server |
| Cocok untuk | Webhook, payment, email, API call | Kalkulasi data, batch processing |
| Timeout | ~60 detik | Tergantung konfigurasi |
Membuat Edge Function
# Install Supabase CLI npm install -g supabase # Login ke akun Supabase supabase login # Inisialisasi project lokal supabase init # Membuat Edge Function baru supabase functions new hello-world # Struktur yang terbentuk: # supabase/ # βββ functions/ # β βββ hello-world/ # β βββ index.ts # βββ config.toml
Contoh Edge Function
// supabase/functions/hello-world/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) => {
// Buat Supabase client di dalam function
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '',
)
// Parse request body
const { name } = await req.json()
// Query database
const { data, error } = await supabaseClient
.from('profiles')
.select('username, bio')
.ilike('username', `%${name}%`)
.limit(5)
if (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
)
}
return new Response(
JSON.stringify({
message: `Hasil pencarian untuk "${name}"`,
results: data,
timestamp: new Date().toISOString()
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
)
})
# Test secara lokal
supabase functions serve hello-world
# Deploy ke Supabase
supabase functions deploy hello-world
# Set environment secrets
supabase secrets set MY_SECRET_KEY=supersecret123
# Test dengan curl
curl -X POST https://xxxxx.supabase.co/functions/v1/hello-world \
-H "Authorization: Bearer YOUR_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "john"}'
# Dari JavaScript client
const { data, error } = await supabase.functions.invoke('hello-world', {
body: { name: 'john' }
})
7. Row Level Security (RLS)
Row Level Security (RLS) adalah fitur PostgreSQL yang memungkinkan Anda mengontrol akses ke setiap baris data berdasarkan role atau atribut pengguna. Di Supabase, RLS adalah lapisan keamanan utama yang memastikan setiap user hanya bisa mengakses data yang menjadi haknya.
Tanpa RLS, setiap user yang menggunakan anon key bisa membaca dan mengubah seluruh data di tabel. Dengan RLS, Anda bisa membuat aturan seperti "user hanya bisa membaca post miliknya sendiri" atau "user hanya bisa mengedit profile sendiri".
Cara Kerja RLS
ββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ β Client β β PostgreSQL β β Data Rows β β Request ββββββΊβ RLS Policies ββββββΊβ (Filtered) β β β β β β β β GET /posts β β 1. Cek user auth β β Hanya rows yang β β β β 2. Evaluasi polisβ β lolos filter β β β β 3. Filter rows β β dikembalikan β ββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ Contoh Policy: "User hanya bisa SELECT post yang published = true ATAU post miliknya sendiri"
Mengaktifkan RLS
-- AKTIFKAN RLS pada tabel (WAJIB untuk keamanan!) ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; ALTER TABLE posts ENABLE ROW LEVEL SECURITY; ALTER TABLE comments ENABLE ROW LEVEL SECURITY; -- Kebijakan untuk tabel profiles: -- Semua orang bisa membaca profile (public) CREATE POLICY "Public profiles are viewable by everyone" ON profiles FOR SELECT USING (true); -- User hanya bisa update profile miliknya sendiri CREATE POLICY "Users can update own profile" ON profiles FOR UPDATE USING (auth.uid() = id) WITH CHECK (auth.uid() = id); -- User baru bisa insert profile sendiri CREATE POLICY "Users can insert own profile" ON profiles FOR INSERT WITH CHECK (auth.uid() = id);
Policy untuk Posts & Comments
-- Posts: Siapa saja bisa baca post yang published CREATE POLICY "Published posts viewable by everyone" ON posts FOR SELECT USING (published = true); -- Posts: Author bisa baca draft miliknya CREATE POLICY "Authors can view own unpublished posts" ON posts FOR SELECT USING ( auth.uid() = author_id ); -- Posts: Authenticated user bisa buat post baru CREATE POLICY "Authenticated users can create posts" ON posts FOR INSERT WITH CHECK ( auth.uid() = author_id AND auth.role() = 'authenticated' ); -- Posts: Author bisa edit post miliknya CREATE POLICY "Authors can update own posts" ON posts FOR UPDATE USING (auth.uid() = author_id) WITH CHECK (auth.uid() = author_id); -- Posts: Author bisa hapus post miliknya CREATE POLICY "Authors can delete own posts" ON posts FOR DELETE USING (auth.uid() = author_id); -- Comments: Semua bisa baca komentar CREATE POLICY "Comments viewable by everyone" ON comments FOR SELECT USING (true); -- Comments: Authenticated user bisa komentar CREATE POLICY "Authenticated users can create comments" ON comments FOR INSERT WITH CHECK ( auth.uid() = user_id AND auth.role() = 'authenticated' ); -- Comments: User bisa edit/hapus komentar sendiri CREATE POLICY "Users can delete own comments" ON comments FOR DELETE USING (auth.uid() = user_id);
Menguji RLS
// Sebagai User A yang login
const { data: myPosts } = await supabase
.from('posts')
.select('*')
// Result: Hanya post yang published + post milik User A (termasuk draft)
// Sebagai User A β mencoba update post milik User B
const { error } = await supabase
.from('posts')
.update({ title: 'Diubah!' })
.eq('id', postMilikUserB)
// Result: Tidak ada error tapi 0 rows affected (RLS memblokir)
// Sebagai anon (tidak login)
const { data: publicPosts } = await supabase
.from('posts')
.select('*')
// Result: Hanya post yang published = true
- Selalu aktifkan RLS untuk setiap tabel yang diakses dari client
- Gunakan
auth.uid()untuk mendapatkan ID user yang sedang login - Gunakan
auth.role()untuk mengecek role (biasanyaauthenticatedatauanon) - Test policy dari berbagai sudut β sebagai owner, sebagai orang lain, dan sebagai anon
- Jangan lupa
FOR INSERTperluWITH CHECK(bukanUSING) - Pertimbangkan menggunakan Database Functions untuk policy yang kompleks
8. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Supabase: