Supabase API & Client SDKs — REST, GraphQL, JavaScript, Flutter, Python
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
apikeyvà JWT trongAuthorization - Fast: Hơn 300% nhanh hơn Firebase cho basic reads
- Scalable: Hàng nghìn concurrent requests
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 Param | Mô tả | Ví dụ |
|---|---|---|
select | Chọn cột, JOIN bảng liên quan | select=id,title,author:author_id(name,avatar) |
order | Sắp xếp kết quả | order=created_at.desc, order=name.asc.nullslast |
limit | Giới hạn số dòng trả về | limit=10 |
offset | Bỏ qua N dòng đầu | offset=20 |
range | Phân trang với header Range | Dù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]:
| Operator | SQL | Mô 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 |
like | LIKE | Pattern matching (case-sensitive) |
ilike | ILIKE | Pattern matching (case-insensitive) |
is | IS | Kiểm tra NULL/TRUE/FALSE |
in | IN | Trong danh sách giá trị |
contains | @> | Chứa (JSONB/array) |
or | OR | Kết hợp điều kiện với OR |
not | NOT | Phủ đị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 browser và Node.js:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://[PROJECT_REF].supabase.co',
'your-publishable-key'
)
Các module chính
| Module | Phương thức chính | Mô 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'
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 curve | Thấp -- query params đơn giản | Trung bình -- cần học GraphQL syntax |
| Over-fetching | Có thể xảy ra | Chỉ lấy đúng field cần |
| Nested data | Dùng embedded resources (select) | Tự nhiên với nested queries |
| Tooling | Postman, curl, browser | GraphiQL IDE built-in |
| Performance | Nhanh hơn cho query đơn giản | Tốt hơn cho query phức tạp nhiều JOIN |
| Best for | CRUD đơn giản, mobile apps | Complex data fetching, nhiều relationships |
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
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
@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ữ | Package | Cài đặt |
|---|---|---|
| Flutter/Dart | supabase_flutter | flutter pub add supabase_flutter |
| Python | supabase | pip install supabase |
| C# | Supabase | dotnet add package Supabase |
| Swift | Supabase | Swift Package Manager |
| Kotlin | supabase-kt | Maven 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
- 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
limitvàoffset - 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ếp và JavaScript SDK
/rest/v1/posts?select=id,title,created_at,author:author_id(name,avatar)&status=eq.published&order=created_at.desc&limit=10&offset=0
- 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/supabasemodule (hoặc tự tạo composableuseSupabase) - 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
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