Database

Supabase untuk Developer: Backend Instan

TOKEN

Panduan lengkap menggunakan Supabase sebagai backend instan β€” mulai dari overview, autentikasi, database PostgreSQL, storage, realtime, Edge Functions, dan Row Level Security

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
DatabasePostgreSQL penuh dengan REST API otomatisPostgreSQL + PostgREST
AuthSistem autentikasi lengkap (email, OAuth, phone)GoTrue
StorageUpload dan serve file (gambar, video, dokumen)S3-compatible
RealtimeSubscribe perubahan data secara livePostgreSQL WAL + WebSocket
Edge FunctionsServerless functions di edge (Deno runtime)Deno + TypeScript
VectorEmbedding dan similarity search untuk AIpgvector
RLSRow Level Security untuk kontrol akses dataPostgreSQL RLS

Supabase vs Firebase

Aspek Supabase Firebase
DatabasePostgreSQL (Relational / SQL)Firestore (NoSQL / Document)
Open Sourceβœ… Ya❌ Proprietary
Self-hostβœ… Bisa (Docker)❌ Tidak bisa
Query LanguageSQL penuh + REST APINoSQL 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 Gratis500 MB DB, 1 GB storage, 50K user1 GB storage, 50K reads/hari
Diagram: Arsitektur Supabase
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        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:

  1. Buka supabase.com dan daftar akun gratis
  2. Klik "New Project" dan isi nama project, password database, dan pilih region
  3. Tunggu sekitar 2 menit hingga project selesai di-provision
  4. Anda akan mendapatkan Project URL dan Anon Key di menu Settings β†’ API
JavaScript β€” Instalasi Client Library
// 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!')
⚠️ Keamanan

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 & PasswordRegistrasi dan login dengan email serta password⭐ Mudah
Magic LinkLogin via link yang dikirim ke email (tanpa password)⭐ Mudah
OAuth (Google, GitHub, dll.)Login dengan akun social media⭐⭐ Sedang
Phone OTPLogin dengan kode verifikasi SMS⭐⭐ Sedang
Anonymous LoginLogin tanpa identitas (guest mode)⭐ Mudah
SSO / SAMLEnterprise single sign-on⭐⭐⭐ Lanjutan

Registrasi dengan Email & Password

JavaScript β€” Registrasi Pengguna
// 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

JavaScript β€” Login Pengguna
// 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)

JavaScript β€” Google OAuth
// 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

JavaScript β€” Session Management
// 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')
  }
}
πŸ’‘ Tips

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:

SQL β€” Membuat Tabel
-- 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

JavaScript β€” CRUD Operations
// ===== 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)

JavaScript β€” Advanced Queries
// 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

SQL β€” Database Function
-- 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;
JavaScript β€” Memanggil RPC
// 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
BucketContainer untuk file β€” mirip folder di S3. Bisa public atau private
ObjectFile individual di dalam bucket
PathLokasi file di dalam bucket (bisa nested, misal: avatars/user1/profile.jpg)
Public BucketFile bisa diakses tanpa autentikasi
Private BucketFile hanya bisa diakses oleh user yang berhak (perlu RLS)
TransformResize, crop, dan ubah format gambar via URL parameter

Membuat Bucket

JavaScript β€” 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

JavaScript β€” Upload & Download
// 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

JavaScript β€” Image Transformation
// 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 ChangesSubscribe perubahan pada tabel (INSERT, UPDATE, DELETE)Dashboard live, notifikasi data
BroadcastKirim pesan antar client (tidak tersimpan di DB)Chat, cursor sharing, game state
PresenceTrack status online/offline userOnline indicators, collaborative editing

Subscribe Perubahan Tabel

JavaScript β€” Realtime Postgres Changes
// 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

JavaScript β€” 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()
      })
    }
  })
⚠️ Perhatian Realtime

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)
RuntimeDeno (TypeScript)PL/pgSQL (di dalam PostgreSQL)
LingkupBisa akses internet, API eksternalHanya database internal
LokasiEdge (dekat user)Database server
Cocok untukWebhook, payment, email, API callKalkulasi data, batch processing
Timeout~60 detikTergantung konfigurasi

Membuat Edge Function

Bash β€” 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

TypeScript β€” 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' }
    }
  )
})
Bash β€” Deploy & Test
# 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

Diagram: Row Level Security Flow
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   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

SQL β€” 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

SQL β€” Posts & Comments Policies
-- 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

JavaScript β€” Test 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
πŸ’‘ Best Practices RLS
  • 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 (biasanya authenticated atau anon)
  • Test policy dari berbagai sudut β€” sebagai owner, sebagai orang lain, dan sebagai anon
  • Jangan lupa FOR INSERT perlu WITH CHECK (bukan USING)
  • 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:

Pertanyaan 1: Database apa yang digunakan Supabase sebagai fondasinya?

a) MongoDB
b) Firestore
c) PostgreSQL
d) MySQL

Pertanyaan 2: Apa fungsi utama Row Level Security (RLS) di Supabase?

a) Mengenkripsi data di database
b) Mengontrol akses setiap baris data berdasarkan user yang sedang login
c) Membuat backup otomatis database
d) Mengompresi ukuran database

Pertanyaan 3: Key apa yang aman digunakan di frontend Supabase?

a) Service Role Key
b) Anon Key
c) Database Password
d) JWT Secret Key

Pertanyaan 4: Runtime apa yang digunakan oleh Supabase Edge Functions?

a) Node.js
b) Python
c) Deno
d) Go

Pertanyaan 5: Fitur Supabase apa yang memungkinkan client menerima perubahan data secara instan tanpa polling?

a) Supabase Auth
b) Supabase Storage
c) Supabase Realtime
d) Supabase Edge Functions
πŸ” Zoom
100%
🎨 Tema