Bỏ qua

Auto-generated CRUD API

PostgREST là gì?

Supabase sử dụng PostgREST để tự động generate REST API từ PostgreSQL schema.

┌─────────────────────────────────────────────────────────────┐
│                    CREATE TABLE users                        │
│                          ↓                                   │
│           PostgREST generates endpoints:                     │
│                                                              │
│  GET    /rest/v1/users          → Select all                │
│  GET    /rest/v1/users?id=eq.1  → Select one                │
│  POST   /rest/v1/users          → Insert                    │
│  PATCH  /rest/v1/users?id=eq.1  → Update                    │
│  DELETE /rest/v1/users?id=eq.1  → Delete                    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Không cần viết API endpoints!


SELECT - Read Data

Select all

const { data, error } = await supabase
  .from('users')
  .select('*');

// SQL: SELECT * FROM users

Select specific columns

const { data, error } = await supabase
  .from('users')
  .select('id, email, full_name');

// SQL: SELECT id, email, full_name FROM users

Select with alias

const { data, error } = await supabase
  .from('users')
  .select('id, email, name:full_name');

// Returns: { id, email, name }

INSERT - Create Data

Insert single row

const { data, error } = await supabase
  .from('users')
  .insert({
    email: 'user@example.com',
    full_name: 'John Doe',
  })
  .select();  // Return inserted row

// SQL: INSERT INTO users (email, full_name)
//      VALUES ('user@example.com', 'John Doe')
//      RETURNING *

Insert multiple rows

const { data, error } = await supabase
  .from('users')
  .insert([
    { email: 'user1@example.com', full_name: 'User 1' },
    { email: 'user2@example.com', full_name: 'User 2' },
    { email: 'user3@example.com', full_name: 'User 3' },
  ])
  .select();

UPDATE - Modify Data

Update single row

const { data, error } = await supabase
  .from('users')
  .update({ full_name: 'Jane Doe' })
  .eq('id', 'user-id-here')
  .select();

// SQL: UPDATE users SET full_name = 'Jane Doe'
//      WHERE id = 'user-id-here'
//      RETURNING *

Update multiple rows

const { data, error } = await supabase
  .from('users')
  .update({ is_active: true })
  .eq('role', 'admin')
  .select();

// Updates all admins

Upsert (Insert or Update)

const { data, error } = await supabase
  .from('users')
  .upsert({
    id: 'existing-or-new-id',
    email: 'user@example.com',
    full_name: 'Updated Name',
  })
  .select();

// Insert if not exists, Update if exists

DELETE - Remove Data

Delete single row

const { error } = await supabase
  .from('users')
  .delete()
  .eq('id', 'user-id-here');

// SQL: DELETE FROM users WHERE id = 'user-id-here'

Delete multiple rows

const { error } = await supabase
  .from('users')
  .delete()
  .eq('is_active', false);

// Delete all inactive users

Soft delete pattern

// Instead of deleting, update deleted_at
const { error } = await supabase
  .from('users')
  .update({ deleted_at: new Date().toISOString() })
  .eq('id', 'user-id-here');

Error Handling

Error structure

const { data, error } = await supabase
  .from('users')
  .select('*');

if (error) {
  console.error('Error:', error.message);
  console.error('Code:', error.code);
  console.error('Details:', error.details);
  console.error('Hint:', error.hint);
}

Common errors

Code Meaning
PGRST116 No rows returned (single() mode)
23505 Unique constraint violation
23503 Foreign key violation
42501 RLS policy violation
42P01 Table doesn't exist

Error handling pattern

async function getUser(id: string) {
  const { data, error } = await supabase
    .from('users')
    .select('*')
    .eq('id', id)
    .single();

  if (error) {
    if (error.code === 'PGRST116') {
      return { user: null, error: 'User not found' };
    }
    throw new Error(error.message);
  }

  return { user: data, error: null };
}

Type Safety

Với generated types

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

type User = Database['public']['Tables']['users']['Row'];
type NewUser = Database['public']['Tables']['users']['Insert'];
type UpdateUser = Database['public']['Tables']['users']['Update'];

// Insert với type safety
const newUser: NewUser = {
  email: 'user@example.com',
  full_name: 'John Doe',
};

const { data, error } = await supabase
  .from('users')
  .insert(newUser)
  .select()
  .single();

// data is typed as User

Count Records

Get count only

const { count, error } = await supabase
  .from('users')
  .select('*', { count: 'exact', head: true });

// count = number of rows, no data returned

Get count with data

const { data, count, error } = await supabase
  .from('users')
  .select('*', { count: 'exact' });

// Both data and count returned

Response Modes

Single row

const { data, error } = await supabase
  .from('users')
  .select('*')
  .eq('id', 'user-id')
  .single();  // Error if 0 or >1 rows

// data is User (not User[])

Maybe single

const { data, error } = await supabase
  .from('users')
  .select('*')
  .eq('id', 'user-id')
  .maybeSingle();  // Returns null if no row

// data is User | null

Hands-on Exercise

CRUD operations

// 1. Create a task
const { data: newTask } = await supabase
  .from('tasks')
  .insert({
    title: 'Learn Supabase',
    completed: false,
  })
  .select()
  .single();

console.log('Created:', newTask);

// 2. Read all tasks
const { data: allTasks } = await supabase
  .from('tasks')
  .select('*');

console.log('All tasks:', allTasks);

// 3. Update task
const { data: updatedTask } = await supabase
  .from('tasks')
  .update({ completed: true })
  .eq('id', newTask.id)
  .select()
  .single();

console.log('Updated:', updatedTask);

// 4. Delete task
const { error } = await supabase
  .from('tasks')
  .delete()
  .eq('id', newTask.id);

console.log('Deleted:', !error);

Tổng kết

CRUD Methods

Method SQL Usage
.select() SELECT Read data
.insert() INSERT Create data
.update() UPDATE Modify data
.upsert() INSERT ON CONFLICT Create or update
.delete() DELETE Remove data

Best Practices

  1. Always use .select() after insert/update to get returned data
  2. Use .single() when expecting exactly one row
  3. Use generated types for type safety
  4. Handle errors properly
  5. Use soft delete for important data

Q&A

  1. Có use case nào cần raw SQL không?
  2. Error handling như thế nào trong app của bạn?
  3. Có cần count feature không?