Supabase (BaaS)

Supabase API & Client SDKs — REST, GraphQL, JavaScript, Flutter, Python

Supabase API reference — REST API (PostgREST), GraphQL (pg_graphql), Client SDKs (JS, Flutter, Python, C#, Swift, Kotlin), CLI.

REST API (Data API -- PostgREST)

Supabase tự động sinh REST API từ database schema qua PostgREST. API có sẵn tại https://[PROJECT_REF].supabase.co/rest/v1/.

Đặc điểm

  • Auto-generated: Schema thay đổi, API tự update -- không cần viết code backend
  • Self-documenting: Supabase tạo documentation trong Dashboard với đầy đủ endpoints và parameters
  • Secure: Tích hợp RLS, API key authentication qua header apikey và JWT trong Authorization
  • Fast: Hơn 300% nhanh hơn Firebase cho basic reads
  • Scalable: Hàng nghìn concurrent requests
Nguyên lý hoạt động: PostgREST tự động map HTTP methods sang SQL: GET -> SELECT, POST -> INSERT, PATCH -> UPDATE, DELETE -> DELETE. URL query params map sang WHERE clause. Đây là lý do Supabase có REST API "instant" mà không cần viết code backend.

CRUD qua REST API

# Read (SELECT) -- lấy tất cả posts
curl 'https://[REF].supabase.co/rest/v1/posts?select=*' \
  -H 'apikey: [ANON_KEY]' \
  -H 'Authorization: Bearer [ANON_KEY]'

# Filter -- lấy posts đã publish, sắp xếp giảm dần, giới hạn 10
curl 'https://[REF].supabase.co/rest/v1/posts?select=id,title&status=eq.published&order=created_at.desc&limit=10' \
  -H 'apikey: [ANON_KEY]'

# Insert -- tạo post mới (cần JWT nếu bảng có RLS)
curl -X POST 'https://[REF].supabase.co/rest/v1/posts' \
  -H 'apikey: [ANON_KEY]' \
  -H 'Authorization: Bearer [JWT]' \
  -H 'Content-Type: application/json' \
  -d '{"title": "Hello", "content": "World"}'

# Update -- cập nhật post theo id
curl -X PATCH 'https://[REF].supabase.co/rest/v1/posts?id=eq.1' \
  -H 'apikey: [ANON_KEY]' \
  -H 'Authorization: Bearer [JWT]' \
  -d '{"title": "Updated"}'

# Delete -- xóa post theo id
curl -X DELETE 'https://[REF].supabase.co/rest/v1/posts?id=eq.1' \
  -H 'apikey: [ANON_KEY]' \
  -H 'Authorization: Bearer [JWT]'

Query Parameters -- Filtering & Sorting

PostgREST hỗ trợ query params mạnh mẽ để lọc, sắp xếp, phân trang trực tiếp qua URL:

Query ParamMô tảVí dụ
selectChọn cột, JOIN bảng liên quanselect=id,title,author:author_id(name,avatar)
orderSắp xếp kết quảorder=created_at.desc, order=name.asc.nullslast
limitGiới hạn số dòng trả vềlimit=10
offsetBỏ qua N dòng đầuoffset=20
rangePhân trang với header RangeDùng .range(0, 9) trong SDK

Horizontal Filtering

Filter bằng cách thêm params vào URL với format [cột]=[operator].[value]:

OperatorSQLMô tả
eq=Bằng
neq!=Khác
gt>Lớn hơn
gte>=Lớn hơn hoặc bằng
lt<Nhỏ hơn
lte<=Nhỏ hơn hoặc bằng
likeLIKEPattern matching (case-sensitive)
ilikeILIKEPattern matching (case-insensitive)
isISKiểm tra NULL/TRUE/FALSE
inINTrong danh sách giá trị
contains@>Chứa (JSONB/array)
orORKết hợp điều kiện với OR
notNOTPhủ định điều kiện
# Filter: status = 'published' AND category IN ('tech', 'science')
/rest/v1/posts?status=eq.published&category=in.(tech,science)

# Filter: price > 100 AND price < 500
/rest/v1/products?price=gt.100&price=lt.500

# Filter: title chứa 'supabase' (không phân biệt hoa thường)
/rest/v1/posts?title=ilike.*supabase*

# OR condition: status = 'draft' OR author_id = 'uuid'
/rest/v1/posts?or=(status.eq.draft,author_id.eq.uuid)

Embedded Resources (JOINs)

# JOIN bảng authors để lấy thông tin author cùng post
/rest/v1/posts?select=id,title,created_at,author:author_id(id,name,avatar)

JavaScript/TypeScript Client (@supabase/supabase-js)

supabase-js là isomorphic JavaScript library, hoạt động cả trên browserNode.js:

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'https://[PROJECT_REF].supabase.co',
  'your-publishable-key'
)

Các module chính

ModulePhương thức chínhMô tả
Database.select(), .insert(), .update(), .delete(), .upsert(), .rpc()Full CRUD với PostgreSQL
Filters.eq(), .neq(), .gt(), .lt(), .gte(), .lte(), .like(), .ilike(), .is(), .in(), .contains(), .or(), .not()Horizontal filtering
Modifiers.limit(), .order(), .range(), .single(), .maybeSingle()Phân trang, sắp xếp, lấy một bản ghi
Auth.signUp(), .signInWithPassword(), .signInWithOAuth(), .signInWithOtp(), .signOut(), .getUser(), .getSession()Xác thực người dùng
Realtime.channel(), .subscribe(), .on(), .track()WebSocket: Broadcast, Presence, Postgres Changes
Storage.from().upload(), .download(), .getPublicUrl(), .list(), .remove()Quản lý file storage
Functions.functions.invoke()Gọi Edge Functions

Full CRUD với supabase-js

// ── SELECT với filters + JOINs + phân trang ──
const { data, error } = await supabase
  .from('posts')
  .select('id, title, author:author_id(name, avatar)')
  .eq('status', 'published')          // status = 'published'
  .order('created_at', { ascending: false }) // mới nhất trước
  .range(0, 9)                        // phân trang: 10 items đầu

// ── INSERT ──
const { data, error } = await supabase
  .from('posts')
  .insert({ title: 'Hello', content: 'World' })
  .select()                           // trả về row vừa insert

// ── UPDATE ──
const { data, error } = await supabase
  .from('posts')
  .update({ status: 'archived' })
  .eq('id', 1)
  .select()

// ── UPSERT ──
const { data, error } = await supabase
  .from('posts')
  .upsert({ id: 1, title: 'Updated' }, { onConflict: 'id' })

// ── DELETE ──
const { data, error } = await supabase
  .from('posts')
  .delete()
  .eq('id', 1)

// ── Gọi Postgres function (RPC) ──
const { data, error } = await supabase
  .rpc('get_user_stats', { user_id: 'uuid-here' })

// ── Auth ──
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password'
})

// ── Realtime subscribe ──
const channel = supabase
  .channel('db-changes')
  .on('postgres_changes',
    { event: 'INSERT', schema: 'public', table: 'messages' },
    (payload) => console.log('New message:', payload.new)
  )
  .subscribe()

// ── Storage ──
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('public/avatar1.png', file)

// ── Edge Functions ──
const { data, error } = await supabase.functions.invoke('hello-world', {
  body: { name: 'Vinh' }
})

Database Modifiers — đầy đủ tất cả filters & modifiers

// ============ FILTERS (toán tử so sánh) ============
.eq('column', value)            // equals: column = value
.neq('column', value)           // not equal: column != value
.gt('column', value)            // greater than: column > value
.gte('column', value)           // greater than or equal: column >= value
.lt('column', value)            // less than: column < value
.lte('column', value)           // less than or equal: column <= value

// ============ PATTERN MATCHING ============
.like('column', '%pattern%')    // SQL LIKE (case-sensitive)
.ilike('column', '%pattern%')   // SQL ILIKE (case-insensitive)

// ============ NULL / BOOLEAN ============
.is('column', null)             // IS NULL
.is('column', true)             // IS TRUE
.is('column', false)            // IS FALSE

// ============ ARRAY / LIST ============
.in('column', [1, 2, 3])        // IN (1, 2, 3)
.in('column', '("a","b")')      // IN ('a', 'b') — string array
.contains('column', { key: 'val' })  // @> cho JSONB (column chứa object này)
.contains('column', [1, 2])     // @> cho array (column chứa tất cả elements)
.containedBy('column', [1, 2])  // <@ cho JSONB/array (column nằm trong array này)
.overlaps('column', [1, 2])     // && cho array/range (có ít nhất 1 phần tử chung)
.rangeLt('range_col', '[1,5)')      // << range strictly left of
.rangeGt('range_col', '[1,5)')      // >> range strictly right of
.rangeGte('range_col', '[1,5)')     // &> range does not extend to the left of
.rangeLte('range_col', '[1,5)')     // &< range does not extend to the right of
.rangeAdjacent('range_col', '[1,5)') // -|- range is adjacent
.rangeUnion('range_col', '[1,5)')   // + range union (có overlap hoặc liền kề)
.rangeIntersect('range_col', '[1,5)')// * range intersection

// ============ FULL TEXT SEARCH ============
.textSearch('column', 'search query')            // full-text search với tsvector
.textSearch('column', 'query', { config: 'english' })  // chỉ định language config
.textSearch('column', 'query', { type: 'plain' })  // plainto_tsquery (AND giữa các từ)
.textSearch('column', 'query', { type: 'websearch' }) // websearch_to_tsquery

// ============ MATCH (multiple eq) ============
.match({ col1: 'val1', col2: 'val2' })   // WHERE col1 = val1 AND col2 = val2

// ============ OR / NOT / FILTER ============
.or('col1.eq.val1, col2.gt.val2')         // OR conditions (comma-separated)
.or('col1.eq.val1, col2.gt.val2', { foreignTable: 'comments' })  // OR trên joined table
.not('column', 'operator', value)         // NOT condition
.filter('column', 'operator', value)      // custom operator (ít dùng hơn)

// ============ MODIFIERS (xử lý kết quả) ============
.order('created_at')                              // ASC mặc định
.order('created_at', { ascending: false })        // DESC
.order('created_at', { ascending: false, nullsFirst: true })  // NULLS FIRST
.order('name', { referencedTable: 'profiles' })   // order by joined table column

.limit(10)                     // LIMIT 10
.range(0, 9)                   // pages 0-9 (0-indexed), header Range
.range(10, 19)                 // pages 10-19 (page 2)

.select()                      // SELECT * (tất cả columns)
.select('id, name')            // SELECT id, name
.select('id, posts(*)')        // SELECT id + JOIN posts (tất cả columns của posts)
.select('id, posts(id, title)') // SELECT id + JOIN posts (chỉ id, title)

.select('*, profiles!inner(*)') // INNER JOIN — chỉ posts có profiles
.select('*, profiles!left(*)')  // LEFT JOIN — tất cả posts kể cả không có profiles
.select('*, profiles!right(*)') // RIGHT JOIN

.single()                      // expect exactly 1 row — throw nếu != 1
.maybeSingle()                 // expect 0 hoặc 1 row — throw nếu > 1
.returns<MyType>()             // TypeScript type hint
.throwOnError()                // throw error thay vì return { error }

// ============ ADVANCED ============
.abortSignal(controller.signal)  // AbortController để cancel request
.explain()                       // EXPLAIN query plan (không chạy query)
.explain({ analyze: true })      // EXPLAIN ANALYZE
.explain({ verbose: true, analyze: true })  // EXPLAIN (ANALYZE, VERBOSE)
.csv()                           // trả về dữ liệu dạng CSV string
.head()                          // HEAD request — trả về headers, không body (kiểm tra existence)
.overrideTypes<MyType>()         // override type casting

Auth Methods — đầy đủ tất cả API

// ============ SIGN UP & SIGN IN ============

// Sign up với email + password
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'secure-password',
  options: {
    data: { full_name: 'Nguyen Van A', role: 'member' },  // user metadata
    emailRedirectTo: 'https://myapp.com/welcome',          // redirect sau khi verify email
  }
})

// Sign in với email + password
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'secure-password',
})

// Sign in với Magic Link (passwordless — gửi link qua email)
const { data, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://myapp.com/auth/callback',
    shouldCreateUser: true,   // tự động tạo user nếu chưa có
  }
})

// Sign in với SMS OTP
const { data, error } = await supabase.auth.signInWithOtp({
  phone: '+84912345678',
  options: { channel: 'sms' }
})

// Sign in với WhatsApp OTP
const { data, error } = await supabase.auth.signInWithOtp({
  phone: '+84912345678',
  options: { channel: 'whatsapp' }
})

// Sign in với OAuth providers
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://myapp.com/auth/callback',
    queryParams: { access_type: 'offline', prompt: 'consent' },
    scopes: 'profile email',   // additional scopes
  }
})

// OAuth providers hỗ trợ:
// 'google', 'github', 'gitlab', 'bitbucket', 'discord',
// 'twitter', 'apple', 'facebook', 'figma', 'kakao',
// 'keycloak', 'linkedin', 'linkedin_oidc', 'notion',
// 'slack', 'slack_oidc', 'spotify', 'twitch',
// 'workos', 'zoom', 'azure', 'fly' (tùy project config)

// Sign in với SSO (Enterprise — SAML 2.0)
const { data, error } = await supabase.auth.signInWithSSO({
  domain: 'company.com',
  options: {
    redirectTo: 'https://myapp.com/auth/callback',
    captchaToken: '...',  // nếu bắt buộc CAPTCHA
  }
})

// Anonymous sign-in (user tạm thời, có thể upgrade lên full account)
const { data, error } = await supabase.auth.signInAnonymously()

// Sign in với token từ 3rd party (Apple Game Center, Sign in with Apple native, etc.)
const { data, error } = await supabase.auth.signInWithIdToken({
  provider: 'apple',
  token: 'id-token-from-apple',
  access_token: '...',  // optional
  nonce: '...',         // optional
})

// ============ SESSION MANAGEMENT ============

// Lấy session hiện tại (từ localStorage ở browser)
const { data: { session }, error } = await supabase.auth.getSession()

// Lấy user hiện tại từ JWT trong session
const { data: { user }, error } = await supabase.auth.getUser()

// Refresh session (dùng refresh token để lấy access token mới)
const { data, error } = await supabase.auth.refreshSession()
const { session, user } = data

// Set session manually (ví dụ: sau khi nhận token từ backend)
const { data, error } = await supabase.auth.setSession({
  access_token: 'eyJ...',
  refresh_token: '...',
})

// Lắng nghe thay đổi trạng thái auth
const { data: { subscription } } = supabase.auth.onAuthStateChange(
  (event, session) => {
    console.log('Auth event:', event)   // SIGNED_IN, SIGNED_OUT, USER_UPDATED,
                                         // USER_DELETED, PASSWORD_RECOVERY,
                                         // TOKEN_REFRESHED, MFA_CHALLENGE_VERIFIED
    console.log('Session:', session)
  }
)
// subscription.unsubscribe() — để cleanup

// Sign out
const { error } = await supabase.auth.signOut()
// Sign out globally (tất cả devices)
const { error } = await supabase.auth.signOut({ scope: 'global' })
// Sign out chỉ trên device hiện tại
const { error } = await supabase.auth.signOut({ scope: 'local' })

// ============ USER MANAGEMENT ============

// Update user profile (email, password, metadata)
const { data, error } = await supabase.auth.updateUser({
  email: 'new-email@example.com',
  password: 'new-password',
  data: { full_name: 'New Name', avatar_url: 'https://...' },
})

// Gửi email reset password
const { data, error } = await supabase.auth.resetPasswordForEmail(
  'user@example.com',
  { redirectTo: 'https://myapp.com/reset-password' }
)

// Reauthenticate (yêu cầu đăng nhập lại trước khi làm thao tác nhạy cảm)
const { data, error } = await supabase.auth.reauthenticate()

// Resend confirmation email / OTP
const { data, error } = await supabase.auth.resend({
  type: 'signup',      // 'signup' | 'email_change' | 'sms' | 'phone_change'
  email: 'user@example.com',
  options: {
    emailRedirectTo: 'https://myapp.com/confirm'
  }
})

// Verify OTP (email hoặc phone)
const { data, error } = await supabase.auth.verifyOtp({
  email: 'user@example.com',
  token: '123456',
  type: 'signup',       // 'signup' | 'magiclink' | 'recovery' | 'invite'
                        // 'email_change' | 'sms' | 'phone_change'
})

// Exchange auth code for session (PKCE flow)
const { data, error } = await supabase.auth.exchangeCodeForSession('auth-code-here')

// Link identity (link Google account vào account đang login)
const { data, error } = await supabase.auth.linkIdentity({
  provider: 'google',
})

// Unlink identity
const { data, error } = await supabase.auth.unlinkIdentity({
  identityId: 'identity-uuid'
})

// Get user identities (linked OAuth accounts)
const { data } = await supabase.auth.getUserIdentities()

// ============ MFA (Multi-Factor Authentication - BẬT THEO PROJECT) ============

// Enroll MFA factor (TOTP - Time-based One-Time Password)
const { data, error } = await supabase.auth.mfa.enroll({
  factorType: 'totp',
  issuer: 'My App',     // tên hiển thị trong authenticator app
  friendlyName: 'user@example.com',
})
// data.totp.qr_code — base64 QR code image
// data.totp.secret — secret key
// data.totp.uri — otpauth:// URI

// Challenge — tạo challenge để verify
const { data, error } = await supabase.auth.mfa.challenge({
  factorId: 'factor-uuid-from-enroll',
})
// data.id — challenge ID, dùng trong verify

// Verify — xác nhận TOTP code
const { data, error } = await supabase.auth.mfa.verify({
  factorId: 'factor-uuid',
  challengeId: 'challenge-id',
  code: '123456',
})

// Unenroll — gỡ bỏ MFA
const { data, error } = await supabase.auth.mfa.unenroll({
  factorId: 'factor-uuid',
})

// List enrolled factors
const { data } = await supabase.auth.mfa.listFactors()
// data.all — tất cả factors
// data.totp — chỉ TOTP factors
// data.phone — chỉ phone factors (nếu bật)

// Get authenticator assurance level (aal)
const { data } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
// data.currentLevel — 'aal1' | 'aal2'
// data.nextLevel — level tiếp theo cần đạt
// data.currentAuthenticationMethods — array of methods đã dùng

// ============ AUTH ADMIN (service_role key required) ============
// Dùng supabaseAdmin client với SERVICE_ROLE_KEY

// Tạo user mới (không cần email verification)
const { data, error } = await supabaseAdmin.auth.admin.createUser({
  email: 'newuser@example.com',
  password: 'password123',
  email_confirm: true,          // auto-confirm email
  phone: '+84912345678',
  phone_confirm: true,          // auto-confirm phone
  user_metadata: { full_name: 'Admin Created User' },
  app_metadata: { role: 'admin' },
})

// Xóa user
const { data, error } = await supabaseAdmin.auth.admin.deleteUser('user-uuid')
// Xóa user + xóa luôn database identity
const { data, error } = await supabaseAdmin.auth.admin.deleteUser('user-uuid', true)

// Update user by ID
const { data, error } = await supabaseAdmin.auth.admin.updateUserById(
  'user-uuid',
  {
    email: 'updated@example.com',
    password: 'new-password',
    email_confirm: true,
    user_metadata: { verified: true },
    app_metadata: { plan: 'pro' },
    ban_duration: null,          // unban user: null = không ban
                                 // '24h' = ban 24 giờ
                                 // 'none' = ban vĩnh viễn
  }
)

// Get user by ID
const { data, error } = await supabaseAdmin.auth.admin.getUserById('user-uuid')

// List users (có phân trang)
const { data, error } = await supabaseAdmin.auth.admin.listUsers({
  page: 1,
  perPage: 50,
})

// Invite user by email
const { data, error } = await supabaseAdmin.auth.admin.inviteUserByEmail(
  'newuser@example.com',
  { redirectTo: 'https://myapp.com/welcome' }
)

// Generate link (signup confirmation, password reset, email change, magic link)
const { data, error } = await supabaseAdmin.auth.admin.generateLink({
  type: 'signup',              // 'signup' | 'magiclink' | 'recovery' | 'email_change_current'
                                // | 'email_change_new' | 'invite'
  email: 'user@example.com',
  password: 'optional-password', // chỉ cần cho signup
  newEmail: 'new@example.com',  // chỉ cần cho email_change_new
  redirectTo: 'https://myapp.com/callback',
  data: { custom_key: 'value' }, // metadata
})

// Delete factor (MFA) for a user
const { data, error } = await supabaseAdmin.auth.admin.deleteFactor(
  'user-uuid',
  'factor-uuid'
)

// Get factors for a user
const { data, error } = await supabaseAdmin.auth.admin.listFactors('user-uuid')

// Send OTP code (SMS)
const { data, error } = await supabaseAdmin.auth.admin.sendOtpCode({
  phone: '+84912345678',
  type: 'sms'
})

Storage Methods — đầy đủ

// ============ BUCKET MANAGEMENT ============
const { data, error } = await supabase.storage.createBucket('avatars', {
  public: true,                     // bucket public
  fileSizeLimit: 5242880,           // 5MB limit
  allowedMimeTypes: ['image/png', 'image/jpeg'],
})
const { data, error } = await supabase.storage.getBucket('avatars')
const { data, error } = await supabase.storage.listBuckets()
const { data, error } = await supabase.storage.updateBucket('avatars', {
  public: false,                    // chuyển sang private
  fileSizeLimit: 10485760,          // tăng lên 10MB
})
const { data, error } = await supabase.storage.deleteBucket('avatars')
const { data, error } = await supabase.storage.emptyBucket('avatars') // xóa tất cả files

// ============ FILE OPERATIONS (from bucket) ============
const bucket = supabase.storage.from('avatars')

// Upload file
const { data, error } = await bucket.upload('public/user1.png', file, {
  cacheControl: '3600',             // Cache-Control header
  contentType: 'image/png',         // Content-Type (auto-detect nếu không set)
  upsert: true,                     // overwrite nếu đã tồn tại
  duplex: 'half',                   // streaming upload
})

// Upload với metadata
const { data, error } = await bucket.upload('public/user1.png', file, {
  metadata: {
    owner: 'user-uuid',
    uploadedAt: new Date().toISOString(),
  }
})

// Download file
const { data, error } = await bucket.download('public/user1.png')
// data là Blob (browser) hoặc ArrayBuffer (Node.js)

// Get public URL (chỉ hoạt động nếu bucket public)
const { data } = bucket.getPublicUrl('public/user1.png')
// data.publicUrl — https://[REF].supabase.co/storage/v1/object/public/avatars/public/user1.png

// Create signed URL (có thời hạn, dùng cho private bucket)
const { data, error } = await bucket.createSignedUrl('private/user1.png', 60)
// data.signedUrl — link hết hạn sau 60 giây

// Create signed upload URL (cho phép upload trực tiếp từ client)
const { data, error } = await bucket.createSignedUploadUrl('uploads/file.png')

// List files trong folder
const { data, error } = await bucket.list('public', {
  limit: 100,
  offset: 0,
  sortBy: { column: 'name', order: 'asc' },
})

// Move/rename file
const { data, error } = await bucket.move('public/user1.png', 'public/renamed.png')

// Copy file
const { data, error } = await bucket.copy('public/user1.png', 'backup/user1.png')

// Remove files (có thể xóa nhiều file cùng lúc)
const { data, error } = await bucket.remove(['public/user1.png', 'public/user2.png'])

// Update file (upsert = true để ghi đè, false để tạo mới)
const { data, error } = await bucket.update('public/user1.png', newFile, {
  upsert: true,
})

// Create signed URLs (nhiều file)
const { data, error } = await bucket.createSignedUrls(
  ['file1.png', 'file2.png'],
  3600 // 1 giờ
)

// Get file info
const { data, error } = await bucket.info('public/user1.png')

// Check file exists
const { data, error } = await bucket.exists('public/user1.png')

// Upload file từ URL (server-side)
const { data, error } = await bucket.upload('public/remote.png',
  await fetch('https://example.com/image.png').then(r => r.blob())
)

Realtime Methods — đầy đủ

// ============ CHANNEL MANAGEMENT ============

// Tạo channel với tất cả các loại subscription
const channel = supabase.channel('room-1', {
  config: {
    broadcast: { self: true },        // nhận broadcast của chính mình
    presence: { key: 'user-id-123' }, // key để track presence
    private: false,                   // channel private (cần auth)
  }
})

// ============ POSTGRES CHANGES (Database CDC) ============

// Lắng nghe tất cả thay đổi trên bảng
channel.on('postgres_changes',
  { event: '*', schema: 'public', table: 'posts' },
  (payload) => {
    console.log('Event type:', payload.eventType)  // INSERT | UPDATE | DELETE
    console.log('New data:', payload.new)           // row mới (INSERT/UPDATE)
    console.log('Old data:', payload.old)           // row cũ (UPDATE/DELETE)
    console.log('Table:', payload.table)
    console.log('Schema:', payload.schema)
    console.log('Commit timestamp:', payload.commit_timestamp)
    console.log('Errors:', payload.errors)
  }
)

// Chỉ lắng nghe INSERT
channel.on('postgres_changes',
  { event: 'INSERT', schema: 'public', table: 'posts' },
  (payload) => { /* ... */ }
)

// Filter theo điều kiện
channel.on('postgres_changes',
  {
    event: 'UPDATE',
    schema: 'public',
    table: 'posts',
    filter: 'user_id=eq.123',         // PostgREST filter syntax
  },
  (payload) => { /* ... */ }
)

// ============ BROADCAST (Real-time messaging) ============

// Gửi broadcast message
await channel.send({
  type: 'broadcast',
  event: 'cursor-move',
  payload: { x: 100, y: 200 },
})

// Nhận broadcast
channel.on('broadcast',
  { event: 'cursor-move' },
  (payload) => {
    console.log('Cursor at:', payload.x, payload.y)
  }
)

// Gửi broadcast với ack (đảm bảo server nhận được)
const resp = await channel.send({
  type: 'broadcast',
  event: 'message',
  payload: { text: 'Hello' },
})
// resp === 'ok'

// ============ PRESENCE (Online status / Multiplayer state) ============

// Track presence state của user hiện tại
await channel.track({
  user_id: 'user-123',
  online_at: new Date().toISOString(),
  status: 'online',
  cursor: { x: 0, y: 0 },
})

// Lắng nghe presence state của tất cả users
channel.on('presence',
  { event: 'sync' },
  () => {
    const state = channel.presenceState()
    // state = {
    //   'user-123': [{ user_id: 'user-123', status: 'online', ... }],
    //   'user-456': [{ user_id: 'user-456', status: 'away', ... }],
    // }
    console.log('Online users:', Object.keys(state))
  }
)

// Lắng nghe khi có user join
channel.on('presence',
  { event: 'join' },
  ({ key, newPresences }) => {
    console.log('User joined:', key, newPresences)
  }
)

// Lắng nghe khi có user leave
channel.on('presence',
  { event: 'leave' },
  ({ key, leftPresences }) => {
    console.log('User left:', key, leftPresences)
  }
)

// Untrack (remove presence)
await channel.untrack()

// ============ SUBSCRIBE & UNSUBSCRIBE ============

// Subscribe — bắt đầu nhận events
channel.subscribe(async (status, err) => {
  if (status === 'SUBSCRIBED') {
    console.log('Connected to channel:', channel.topic)
  }
  if (status === 'CHANNEL_ERROR') {
    console.error('Channel error:', err)
  }
  if (status === 'TIMED_OUT') {
    console.warn('Channel timed out — retrying...')
  }
  if (status === 'CLOSED') {
    console.log('Channel closed')
  }
})

// Unsubscribe channel cụ thể
await supabase.removeChannel(channel)

// Unsubscribe tất cả channels
await supabase.removeAllChannels()

// Get tất cả channels đang active
const channels = supabase.getChannels()

// ============ CONNECTION STATUS ============

// Kiểm tra kết nối Realtime
if (supabase.realtime.isConnected()) {
  console.log('Realtime connected')
}

// Manually connect (auto-connect khi có channel đầu tiên subscribe)
supabase.realtime.connect()

// Manually disconnect
supabase.realtime.disconnect()

// ============ REALTIME TOPICS ============
// Mỗi channel có một topic unique:
// 'realtime:public:posts' — dùng topic prefix của Supabase Realtime v2

// ============ FULL EXAMPLE: Real-time Chat Room ============

async function joinChatRoom(roomId, userId) {
  const channel = supabase.channel(`room:${roomId}`)

  // Lắng nghe tin nhắn mới
  channel.on('broadcast', { event: 'new-message' }, (payload) => {
    addMessageToUI(payload)
  })

  // Track presence (ai đang online)
  channel.on('presence', { event: 'sync' }, () => {
    updateOnlineUsersList(channel.presenceState())
  })

  channel.subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({ user_id: userId, joined_at: new Date().toISOString() })
    }
  })

  return channel
}

async function sendMessage(channel, text) {
  await channel.send({
    type: 'broadcast',
    event: 'new-message',
    payload: { text, sender: userId, timestamp: new Date().toISOString() },
  })
}

Type Generation -- Type-safe Queries

Supabase hỗ trợ generate TypeScript types từ database schema:

# Generate types từ local database
supabase gen types typescript --local > types/supabase.ts

# Generate types từ linked remote project
supabase gen types typescript --linked > types/supabase.ts

Sau khi generate, bạn có type-safe queries:

import { Database } from '@/types/supabase'

const supabase = createClient<Database>(url, key)

// TypeScript sẽ báo lỗi nếu bạn query sai tên bảng hoặc cột
const { data } = await supabase
  .from('posts')            // ✅ auto-complete: 'posts'
  .select('id, title')      // ✅ auto-complete: 'id' | 'title' | 'content' | ...
  .eq('status', 'published') // ✅ auto-complete: 'draft' | 'published' | 'archived'
Type generation là best practice khi làm việc với Supabase + TypeScript. Nó giúp phát hiện lỗi schema mismatch ngay khi compile, auto-complete column names, và đảm bảo type safety cho toàn bộ data layer.
Interview tip — supabase-js: SDK supabase-js là isomorphic (chạy được cả browser và Node.js). Filter chain dùng builder pattern (supabase.from('table').select().eq().order()). Auth module tích hợp sẵn OAuth, MFA, session management không cần cấu hình thêm. Realtime module dựa trên WebSocket, hỗ trợ 3 pattern: Postgres Changes (CDC), Broadcast (messaging), Presence (online tracking).

GraphQL API (pg_graphql)

GraphQL endpoint: https://[PROJECT_REF].supabase.co/graphql/v1

# Query GraphQL -- lấy 10 posts mới nhất kèm thông tin author
query {
  postsCollection(first: 10, orderBy: [{ created_at: DescNullsLast }]) {
    edges {
      node {
        id
        title
        created_at
        author {
          name
          avatar
        }
      }
    }
  }
}
# Gọi GraphQL API qua curl
curl -X POST 'https://[REF].supabase.co/graphql/v1' \
  -H 'apikey: [ANON_KEY]' \
  -H 'Authorization: Bearer [ANON_KEY]' \
  -H 'Content-Type: application/json' \
  --data-raw '{"query": "{ postsCollection(first: 5) { edges { node { id title } } } }", "variables": {}}'

Supabase Studio có built-in GraphiQL IDE để khám phá và test GraphQL API.

REST vs GraphQL

Tiêu chíREST (PostgREST)GraphQL (pg_graphql)
Learning curveThấp -- query params đơn giảnTrung bình -- cần học GraphQL syntax
Over-fetchingCó thể xảy raChỉ lấy đúng field cần
Nested dataDùng embedded resources (select)Tự nhiên với nested queries
ToolingPostman, curl, browserGraphiQL IDE built-in
PerformanceNhanh hơn cho query đơn giảnTốt hơn cho query phức tạp nhiều JOIN
Best forCRUD đơn giản, mobile appsComplex data fetching, nhiều relationships
Cả REST và GraphQL đều auto-generated từ schematích hợp RLS. Bạn có thể dùng cả hai trong cùng một dự án -- không cần chọn một.

Supabase CLI — Reference đầy đủ

Cài đặt

# npm (toàn cầu)
npm install -g supabase

# macOS Homebrew
brew install supabase/tap/supabase

# Windows Scoop
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase

# macOS / Linux (curl)
curl -fsSL https://cli.supabase.com/install.sh | sh

# Kiểm tra version
supabase --version

Local Development

# ============ LOCAL DEVELOPMENT ============

supabase init                          # Khởi tạo project Supabase local (tạo thư mục supabase/)
                                       # Output: config.toml, thư mục migrations/, functions/, seeds/

supabase start                         # Start tất cả services (Docker containers):
                                       # - Postgres (port 54322)
                                       # - Studio (port 54323)
                                       # - Inbucket (mail testing, port 54324)
                                       # - Kong API Gateway
                                       # - GoTrue (Auth)
                                       # - PostgREST
                                       # - Realtime
                                       # - Storage API
                                       # - Edge Functions (Deno)

supabase start --exclude realtime      # Start trừ Realtime service

supabase stop                          # Stop tất cả services — giữ nguyên data
supabase stop --no-backup              # Stop và không backup data

supabase status                        # Xem trạng thái tất cả containers
                                       # Output: tên service, status (running/stopped),
                                       # ports, health check

Database Commands

# ============ DATABASE ============

supabase db pull                       # Pull schema từ remote về local migration file
                                       # Tạo 1 migration file mới chứa toàn bộ schema remote

supabase db push                       # Push migration lên remote database
                                       # Chạy tất cả migration files chưa apply
                                       # Tương đương: supabase migration up + db push

supabase db reset                      # Reset local database (xóa data, chạy lại migrations + seeds)
                                       # Dùng khi: schema thay đổi lớn, test lại từ đầu

supabase db dump                       # Dump schema + data từ remote database
                                       # Options:
                                       #   --data-only       chỉ dump data
                                       #   --schema-only     chỉ dump schema
                                       #   --file output.sql lưu vào file
                                       #   supabase db dump -f seed.sql --data-only

supabase db diff                       # Diff local schema vs remote schema
                                       # Options:
                                       #   --linked        diff với linked project
                                       #   --file diff.sql lưu diff vào file
                                       #   --schema public chỉ diff schema public

supabase db diff --use-migra           # Dùng migra tool để diff (chi tiết hơn)

supabase db lint                       # Lint database schema
                                       # Kiểm tra: RLS policies, unused indexes,
                                       # security vulnerabilities, naming conventions

Migrations

# ============ MIGRATIONS ============

supabase migration new <name>          # Tạo migration file mới
                                       # File: supabase/migrations/<timestamp>_<name>.sql
                                       # Ví dụ: supabase migration new add_posts_table
                                       # → supabase/migrations/20240101000000_add_posts_table.sql

supabase migration list                # List tất cả migrations (local + remote + status)
                                       # Output: version, name, applied_at (local/remote)

supabase migration up                  # Apply tất cả pending migrations lên database
                                       # Options:
                                       #   --local    apply lên local DB
                                       #   --linked   apply lên linked remote DB
                                       #   --db-url   apply lên database URL cụ thể

supabase migration repair <version>    # Repair migration history (không chạy SQL)
                                       # Đánh dấu migration là đã applied mà không chạy
                                       # Dùng khi: migration đã chạy thủ công, cần sync lịch sử
                                       # Ví dụ: supabase migration repair 20240101000000 --status applied

supabase migration squash              # Gộp tất cả migrations thành 1 file duy nhất
                                       # Hữu ích khi có quá nhiều migration files
                                       # Tạo 1 migration mới chứa toàn bộ schema final
                                       # Các migration cũ được xóa khỏi danh sách

Generate Types

# ============ GENERATE TYPES ============

supabase gen types typescript          # Generate TypeScript types từ DB schema
                                       # Output: type definitions cho tất cả tables, views

supabase gen types typescript --local  # Từ local development database
                                       # supabase gen types typescript --local > src/types/supabase.ts

supabase gen types typescript --linked # Từ linked remote project
                                       # supabase gen types typescript --linked > types/database.ts

supabase gen types typescript --db-url postgresql://... # Từ database URL cụ thể

supabase gen types typescript --schemas public,private  # Chỉ generate các schema cụ thể

Edge Functions (Deno)

# ============ EDGE FUNCTIONS ============

supabase functions new <name>          # Tạo Edge Function mới (Deno)
                                       # File: supabase/functions/<name>/index.ts
                                       # Có template mặc định
                                       # supabase functions new send-email

supabase functions serve               # Serve functions locally (hot reload)
                                       # Options:
                                       #   --env-file .env.development

supabase functions deploy <name>       # Deploy function lên production
                                       # supabase functions deploy send-email
                                       # deploy tất cả: supabase functions deploy (không truyền name)

supabase functions deploy <name> --no-verify-jwt  # Deploy không verify JWT (public function)

supabase functions delete <name>       # Xóa function khỏi production
                                       # supabase functions delete send-email

supabase functions list                # List tất cả deployed Edge Functions
                                       # Output: name, version, status, created_at

supabase functions download <name>     # Tải function code về local
                                       # Khi function đã được deploy từ máy khác
                                       # supabase functions download send-email --output-dir ./downloads

supabase functions logs <name>         # Xem logs của function
                                       # Options:
                                       #   --limit 100
                                       #   --follow / -f     tail mode (real-time)

Secrets Management

# ============ SECRETS (cho Edge Functions) ============

supabase secrets set <key>=<value>     # Set secret cho Edge Functions
                                       # supabase secrets set STRIPE_API_KEY=sk_live_xxx
                                       # Có thể set nhiều secrets cùng lúc:
                                       # supabase secrets set KEY1=val1 KEY2=val2

supabase secrets set --env-file .env   # Set tất cả biến từ .env file

supabase secrets list                  # List tất cả secrets
                                       # Chỉ hiển thị key names, KHÔNG hiển thị values
                                       # Output: name, created_at

supabase secrets unset <key>           # Xóa secret
                                       # supabase secrets unset STRIPE_API_KEY

Projects Management

# ============ PROJECTS ============

supabase projects create <name>        # Tạo project mới trên Supabase Platform
                                       # Options:
                                       #   --org-id <id>         organization ID
                                       #   --db-password <pass>  database password
                                       #   --region <region>     region (ap-southeast-1, us-east-1, etc.)
                                       #   --plan free|pro|team   plan type

supabase projects list                 # List tất cả projects của bạn
                                       # Output: id, name, organization, region, created_at

supabase projects delete <id>          # Xóa project (không thể undo!)
                                       # supabase projects delete abcdefghijklmnop

supabase projects api-keys             # Hiển thị API keys của project
                                       # Output: name, type (anon/service_role/publishable), api_key

supabase link --project-ref <ref>      # Link local project với remote project
                                       # supabase link --project-ref abcdefghijklmnop
                                       # Options:
                                       #   --password <pass>    database password

supabase unlink                        # Bỏ link local với remote

supabase login                         # Đăng nhập vào Supabase (tạo access token)
                                       # Mở browser để authorize

supabase logout                        # Đăng xuất

Organizations

# ============ ORGANIZATIONS ============

supabase orgs create <name>            # Tạo organization mới
                                       # supabase orgs create "My Team"

supabase orgs list                     # List tất cả organizations của bạn
                                       # Output: id, name

Branches (Preview Environments)

# ============ BRANCHES ============

supabase branches create <name>        # Tạo preview branch
                                       # Branch là 1 database instance độc lập
                                       # Dùng để test migrations trước khi merge
                                       # supabase branches create feat-new-schema

supabase branches list                 # List tất cả branches
                                       # Output: name, status, created_at, database_url

supabase branches delete <name>        # Xóa branch

supabase branches update <name>        # Cập nhật branch config

Config

# ============ CONFIG ============

supabase config push                   # Push config từ config.toml lên remote project
                                       # Các settings: auth, db, storage, realtime, edge_functions

Auth / SSO

# ============ AUTH / SSO ============

supabase sso add <type>                # Thêm identity provider (SSO/SAML)
                                       # supabase sso add saml
                                       # Options:
                                       #   --metadata-url <url>          SAML metadata URL
                                       #   --metadata-file <file>        SAML metadata XML file
                                       #   --domains <domain1,domain2>   email domains
                                       #   --attribute-mapping-file <file>  custom attribute mapping

supabase sso list                      # List tất cả SSO providers
                                       # Output: id, type, domains, created_at

supabase sso show <id>                 # Xem thông tin chi tiết của 1 provider
                                       # Output: full config, domains, attribute mapping

supabase sso update <id>               # Update provider config
                                       # supabase sso update <id> --domains newdomain.com

supabase sso remove <id>               # Xóa SSO provider

Storage

# ============ STORAGE ============

supabase storage ls                    # List tất cả storage buckets
supabase storage ls <bucket>           # List files trong bucket
                                       # supabase storage ls avatars --prefix public/

supabase storage cp <src> <dest>       # Upload/download files
                                       # Upload local → remote:
                                       #   supabase storage cp ./image.png ss://my-bucket/path/image.png
                                       # Download remote → local:
                                       #   supabase storage cp ss://my-bucket/path/image.png ./downloads/
                                       # Copy giữa các buckets:
                                       #   supabase storage cp ss://bucket1/file.png ss://bucket2/file.png

supabase storage rm <path>             # Delete storage objects
                                       # supabase storage rm ss://my-bucket/path/image.png

Network Management

# ============ NETWORK ============

supabase bans list                     # List network bans (IP/range bị chặn)
supabase network-restrictions          # Manage network restrictions (allowlist/blocklist)

Testing

# ============ TESTING (pgTAP) ============

supabase test new <name>               # Tạo test file mới
                                       # File: supabase/tests/<name>.sql
                                       # Template sẵn với pgTAP assertions

supabase test db                       # Chạy tất cả database tests
                                       # Dùng pgTAP framework

Autocomplete / Shell Completion

# ============ COMPLETION ============

supabase completion bash               # Generate bash autocomplete script
                                       # Dùng: source <(supabase completion bash)
                                       # Hoặc thêm vào ~/.bashrc

supabase completion zsh                # Generate zsh autocomplete script
                                       # Dùng: source <(supabase completion zsh)
                                       # Hoặc thêm vào ~/.zshrc

supabase completion powershell         # Generate PowerShell autocomplete script

supabase completion fish               # Generate Fish shell autocomplete script

Global Flags

# ============ GLOBAL FLAGS (dùng với mọi command) ============

# --debug
# Output debug logs chi tiết (request/response, SQL queries, timing)
# supabase start --debug

# --experimental
# Enable tính năng experimental (có thể không ổn định)
# supabase db push --experimental

# --output json
# JSON output để scripting/automation (thay vì table format)
# supabase projects list --output json

# --create-ticket
# Tự động tạo support ticket trên Supabase nếu command gặp lỗi
# supabase db push --create-ticket

# --workdir <path>
# Chỉ định thư mục làm việc (thay vì thư mục hiện tại)
# supabase start --workdir ./my-project

# --version
# Hiển thị version của CLI
# supabase --version
Interview tip — CLI: Supabase CLI là công cụ quan trọng cho local development. Các command hay dùng nhất: supabase start (khởi động toàn bộ stack local qua Docker), supabase db push/pull (đồng bộ schema), supabase migration new (tạo migration), supabase gen types (generate TypeScript types). Local development stack bao gồm Postgres, Auth (GoTrue), PostgREST, Realtime, Storage, và Edge Functions — tất cả chạy qua Docker.

Quickstarts -- Code mẫu theo framework

Nuxt 3 / Vue Quickstart

1. Tạo Supabase project: vào supabase.com/dashboard tạo project mới.

2. Setup database:

-- Tạo bảng instruments
CREATE TABLE instruments (
  id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
  name TEXT NOT NULL
);

INSERT INTO instruments (name) VALUES ('violin'), ('viola'), ('cello');

-- Enable RLS + grant quyền
ALTER TABLE instruments ENABLE ROW LEVEL SECURITY;
GRANT SELECT ON public.instruments TO anon;

CREATE POLICY "public can read instruments"
ON public.instruments FOR SELECT TO anon USING (true);

3. Tạo Nuxt app:

npx nuxi@latest init my-app
cd my-app && npm install @supabase/supabase-js

4. Cấu hình .env:

SUPABASE_URL=https://[PROJECT_REF].supabase.co
SUPABASE_PUBLISHABLE_KEY=eyJhbG...[your-anon-key]

5. Cấu hình nuxt.config.ts:

export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      supabaseUrl: process.env.SUPABASE_URL,
      supabasePublishableKey: process.env.SUPABASE_PUBLISHABLE_KEY
    }
  }
})

6. Query data trong app.vue:

<script setup>
import { createClient } from '@supabase/supabase-js'

const config = useRuntimeConfig()
const supabase = createClient(
  config.public.supabaseUrl,
  config.public.supabasePublishableKey
)
const instruments = ref([])

async function getInstruments() {
  const { data } = await supabase.from('instruments').select()
  instruments.value = data
}

onMounted(() => { getInstruments() })
</script>

<template>
  <ul>
    <li v-for="instrument in instruments" :key="instrument.id">
      {{ instrument.name }}
    </li>
  </ul>
</template>

7. Chạy app:

npm run dev
Có community-maintained module @nuxtjs/supabase cung cấp DX tốt hơn cho Nuxt với composables như useSupabaseClient(), useSupabaseUser(), useSupabaseSession(). Nên dùng module này thay vì tự tạo client trong production.

React + Vite Quickstart

npm create vite@latest my-app -- --template react
cd my-app && npm install @supabase/supabase-js
// App.jsx
import { createClient } from '@supabase/supabase-js'
import { useEffect, useState } from 'react'

const supabase = createClient(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
)

function App() {
  const [instruments, setInstruments] = useState([])

  useEffect(() => { getInstruments() }, [])

  async function getInstruments() {
    const { data } = await supabase.from('instruments').select()
    setInstruments(data)
  }

  return (
    <ul>
      {instruments.map((inst) => (
        <li key={inst.id}>{inst.name}</li>
      ))}
    </ul>
  )
}

export default App

Next.js Quickstart (App Router)

npx create-next-app -e with-supabase
// app/instruments/page.tsx
import { createClient } from '@/utils/supabase/server'
import { Suspense } from 'react'

async function InstrumentsData() {
  const supabase = await createClient()
  const { data: instruments } = await supabase.from('instruments').select()

  return <pre>{JSON.stringify(instruments, null, 2)}</pre>
}

export default function Instruments() {
  return (
    <Suspense fallback={<div>Loading instruments...</div>}>
      <InstrumentsData />
    </Suspense>
  )
}

Flutter Quickstart

import 'package:supabase_flutter/supabase_flutter.dart';

Future<void> main() async {
  await Supabase.initialize(
    url: 'https://[PROJECT_REF].supabase.co',
    anonKey: 'your-anon-key',
  );
  runApp(MyApp());
}

// Query data
final data = await Supabase.instance.client
  .from('instruments')
  .select();

Auth Setup -- dùng chung cho mọi framework

// signUp
const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'password123'
})

// signIn
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@example.com',
  password: 'password123'
})

// signIn with OAuth
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: { redirectTo: 'https://myapp.com/auth/callback' }
})

// signOut
await supabase.auth.signOut()

// Listen auth state changes
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') console.log('User signed in:', session.user)
  if (event === 'SIGNED_OUT') console.log('User signed out')
})

Realtime Subscription pattern

const channel = supabase
  .channel('user-posts')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'posts',
      filter: `user_id=eq.${user.id}`
    },
    (payload) => {
      if (payload.eventType === 'INSERT') {
        posts.value = [payload.new, ...posts.value]
      }
      if (payload.eventType === 'DELETE') {
        posts.value = posts.value.filter(p => p.id !== payload.old.id)
      }
    }
  )
  .subscribe()

Các SDK khác

Ngôn ngữPackageCài đặt
Flutter/Dartsupabase_flutterflutter pub add supabase_flutter
Pythonsupabasepip install supabase
C#Supabasedotnet add package Supabase
SwiftSupabaseSwift Package Manager
Kotlinsupabase-ktMaven Central
// Flutter
import 'package:supabase_flutter/supabase_flutter.dart';
await Supabase.initialize(url: '...', anonKey: '...');
final data = await Supabase.instance.client.from('posts').select();
# Python
from supabase import create_client
supabase = create_client('https://[REF].supabase.co', 'anon-key')
data = supabase.table('posts').select('*').execute()

Management API

GET    https://api.supabase.com/v1/projects          # Liệt kê projects
POST   https://api.supabase.com/v1/projects          # Tạo project mới
DELETE https://api.supabase.com/v1/projects/{ref}    # Xóa project

GET    https://api.supabase.com/v1/organizations     # Liệt kê organizations
POST   https://api.supabase.com/v1/organizations     # Tạo organization

Bài tập thực hành

Bài tập 1: Build Blog API với filters
Xây dựng một blog API sử dụng Supabase REST API, đáp ứng các yêu cầu:
  • Chỉ hiển thị bài đã publish (status = 'published')
  • Sắp xếp theo ngày tạo giảm dần (mới nhất trước)
  • Phân trang: mỗi trang 10 bài, hỗ trợ chuyển trang qua limitoffset
  • Tìm kiếm bài viết theo title (dùng ilike)
  • Lọc theo category (dùng in)
  • Join với bảng authors để hiển thị tên và avatar tác giả
  • Triển khai cả bằng REST API trực tiếpJavaScript SDK
Mẫu query REST:
/rest/v1/posts?select=id,title,created_at,author:author_id(name,avatar)&status=eq.published&order=created_at.desc&limit=10&offset=0
Bài tập 2: Type-safe queries với generated types
Trong một dự án Nuxt 3:
  • Setup Supabase + generate TypeScript types từ remote database
  • Tạo một PostList component hiển thị danh sách bài viết với type-safe queries
  • Tạo một PostForm component cho phép tạo bài mới, validate form với TypeScript types
  • Sử dụng @nuxtjs/supabase module (hoặc tự tạo composable useSupabase)
  • Triển khai real-time cập nhật danh sách bài viết khi có bài mới
  • Viết một Edge Function để xử lý webhook nhận comment mới từ bên thứ ba
Gợi ý cấu trúc thư mục:
composables/
  useSupabase.ts       # Singleton client
  usePosts.ts          # Posts CRUD composable
types/
  supabase.ts          # Generated types
components/
  PostList.vue
  PostForm.vue
server/
  api/
    webhooks/
      comments.post.ts # Nitro server route xử lý webhook

© 2026 .NET Fresher Guide. All rights reserved.

Về Trang Web

Hướng dẫn toàn diện để chuẩn bị phỏng vấn vị trí .NET Fresher với nội dung từ lý thuyết đến thực hành.