Supabase JavaScript Client
Cài đặt và Setup
Installation
# npm
npm install @supabase/supabase-js
# yarn
yarn add @supabase/supabase-js
# pnpm
pnpm add @supabase/supabase-js
Basic Setup
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key'
);
With TypeScript Types
import { createClient } from '@supabase/supabase-js';
import { Database } from './types/supabase';
const supabase = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// Now you get type hints for tables and columns
const { data } = await supabase
.from('tasks') // Autocomplete!
.select('id, title, status');
Generate TypeScript Types
Using CLI
# Generate types from remote database
supabase gen types typescript --project-id your-project-id > types/supabase.ts
# Generate from local database
supabase gen types typescript --local > types/supabase.ts
# In package.json
{
"scripts": {
"gen:types": "supabase gen types typescript --project-id $PROJECT_ID > types/supabase.ts"
}
}
Generated Types Structure
// types/supabase.ts
export type Database = {
public: {
Tables: {
tasks: {
Row: {
id: string;
title: string;
status: string;
user_id: string;
created_at: string;
};
Insert: {
id?: string;
title: string;
status?: string;
user_id: string;
created_at?: string;
};
Update: {
id?: string;
title?: string;
status?: string;
user_id?: string;
created_at?: string;
};
};
};
};
};
Query Patterns
Select
// Basic select
const { data, error } = await supabase
.from('tasks')
.select('*');
// Select specific columns
const { data } = await supabase
.from('tasks')
.select('id, title, status');
// With relations
const { data } = await supabase
.from('tasks')
.select(`
id,
title,
project:projects(name),
assignee:profiles(full_name, avatar_url)
`);
// Count only
const { count } = await supabase
.from('tasks')
.select('*', { count: 'exact', head: true });
Filters
// Equal
.eq('status', 'active')
// Not equal
.neq('status', 'deleted')
// Greater/Less than
.gt('priority', 3)
.gte('priority', 3)
.lt('priority', 3)
.lte('priority', 3)
// Pattern matching
.like('title', '%task%')
.ilike('title', '%Task%') // Case insensitive
// In array
.in('status', ['active', 'pending'])
// Contains (array column)
.contains('tags', ['urgent'])
// Is null
.is('deleted_at', null)
// Range
.range(0, 9) // First 10 records
// Combine with AND
.eq('status', 'active')
.gt('priority', 2)
// OR conditions
.or('status.eq.active,status.eq.pending')
Insert
// Single insert
const { data, error } = await supabase
.from('tasks')
.insert({
title: 'New Task',
status: 'todo',
user_id: userId,
})
.select()
.single();
// Multiple insert
const { data, error } = await supabase
.from('tasks')
.insert([
{ title: 'Task 1', user_id: userId },
{ title: 'Task 2', user_id: userId },
])
.select();
// Upsert (insert or update)
const { data, error } = await supabase
.from('tasks')
.upsert({
id: existingId, // If exists, update
title: 'Updated Title',
})
.select()
.single();
Update
// Update by ID
const { data, error } = await supabase
.from('tasks')
.update({ status: 'done' })
.eq('id', taskId)
.select()
.single();
// Update multiple
const { data, error } = await supabase
.from('tasks')
.update({ status: 'archived' })
.eq('project_id', projectId)
.lt('updated_at', '2024-01-01')
.select();
Delete
// Delete by ID
const { error } = await supabase
.from('tasks')
.delete()
.eq('id', taskId);
// Delete multiple
const { error } = await supabase
.from('tasks')
.delete()
.eq('status', 'deleted')
.lt('deleted_at', thirtyDaysAgo);
Order
// Single column
const { data } = await supabase
.from('tasks')
.select('*')
.order('created_at', { ascending: false });
// Multiple columns
const { data } = await supabase
.from('tasks')
.select('*')
.order('priority', { ascending: false })
.order('created_at', { ascending: false });
// Null handling
const { data } = await supabase
.from('tasks')
.select('*')
.order('due_date', { ascending: true, nullsFirst: false });
// Offset pagination
const page = 1;
const pageSize = 10;
const { data, count } = await supabase
.from('tasks')
.select('*', { count: 'exact' })
.range((page - 1) * pageSize, page * pageSize - 1);
// Cursor pagination (better for large datasets)
const { data } = await supabase
.from('tasks')
.select('*')
.gt('id', lastSeenId)
.order('id')
.limit(10);
RPC (Remote Procedure Call)
Call Database Functions
// Call function without params
const { data, error } = await supabase.rpc('get_active_users');
// Call with parameters
const { data, error } = await supabase.rpc('get_user_tasks', {
user_uuid: userId,
status_filter: 'active',
});
// Function returning single row
const { data, error } = await supabase
.rpc('get_user_stats', { user_uuid: userId })
.single();
Error Handling
Handle Errors
const { data, error } = await supabase
.from('tasks')
.select('*');
if (error) {
console.error('Error code:', error.code);
console.error('Error message:', error.message);
console.error('Error details:', error.details);
console.error('Error hint:', error.hint);
// Handle specific errors
if (error.code === 'PGRST116') {
// No rows returned
}
if (error.code === '23505') {
// Unique constraint violation
}
if (error.code === '42501') {
// RLS violation
}
throw new Error(error.message);
}
// Use data safely
console.log(data);
TypeScript Error Handling
// Type-safe error handling
type QueryResult<T> = {
data: T | null;
error: Error | null;
};
async function getTasks(): Promise<Task[]> {
const { data, error } = await supabase
.from('tasks')
.select('*');
if (error) throw error;
return data ?? [];
}
Client Configuration
Advanced Options
const supabase = createClient<Database>(url, key, {
auth: {
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
},
global: {
headers: {
'x-custom-header': 'value',
},
},
db: {
schema: 'public',
},
realtime: {
params: {
eventsPerSecond: 10,
},
},
});
Tổng kết
Query Cheat Sheet
// CRUD
.select('*')
.insert({ ... })
.update({ ... })
.delete()
// Filters
.eq('col', value)
.neq('col', value)
.gt('col', value)
.gte('col', value)
.lt('col', value)
.lte('col', value)
.in('col', [values])
.is('col', null)
// Modifiers
.order('col', { ascending: false })
.limit(10)
.range(0, 9)
.single()
// RPC
.rpc('function_name', { params })
Best Practices
✅ Generate and use TypeScript types
✅ Handle errors properly
✅ Use .single() for expected single row
✅ Use select() after insert/update to get returned data
✅ Use RPC for complex operations
Q&A
- TypeScript types đã generate chưa?
- Error handling pattern hiện tại?
- Có dùng RPC functions không?