Chương 3: Supabase — Cloud Database Cho Team

"Đức nói: 'SQLite là đủ cho prototype.' Lan phản bác: 'Team 5 người cùng dev, cần cloud database. Supabase free tier đủ mạnh.' Tuấn đã setup xong trong 10 phút."
🎯 Mục tiêu
- Tạo Supabase project & hiểu kiến trúc
- Thiết kế schema cho TeamFlow (PostgreSQL)
- Setup Supabase Auth, Storage, Realtime
- Kết nối từ Express app
Phần 1: Tại Sao Supabase? (15 phút)
So Sánh Database Options
| Feature | SQLite | Firebase | Supabase ✅ |
|---|---|---|---|
| Type | File-based | NoSQL | PostgreSQL |
| Team access | ❌ Local only | ✅ Cloud | ✅ Cloud |
| Auth built-in | ❌ | ✅ | ✅ |
| Storage | ❌ | ✅ | ✅ |
| Realtime | ❌ | ✅ | ✅ |
| SQL | ✅ | ❌ | ✅ |
| Free tier | N/A | Limited | 500MB DB, 1GB Storage |
| Open source | N/A | ❌ | ✅ |
Supabase Architecture
┌──────────────────────────────────────┐
│ Supabase Platform │
├──────────┬───────────┬──────────────┤
│ Database │ Auth │ Storage │
│(Postgres)│(JWT/OAuth)│(S3-like) │
├──────────┼───────────┼──────────────┤
│ Realtime │ Edge Fn │ REST API │
│(WebSocket│(Deno) │(PostgREST) │
└──────────┴───────────┴──────────────┘
↑ All accessible via supabase-js clientPhần 2: Setup Supabase Project (25 phút)
Bước 1: Tạo Project
- Vào supabase.com → New Project
- Đặt tên:
teamflow - Database password: (ghi nhớ!)
- Region: Southeast Asia (Singapore)
- Chờ 2 phút setup
Bước 2: Lấy Credentials
bash
# Từ Supabase Dashboard → Settings → API
# Copy 2 giá trị:
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIs... # Server-side only!Bước 3: Setup trong Project
bash
# Trong teamflow-app/
npm install @supabase/supabase-js
# Tạo .env
cat > .env << EOF
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_KEY=your-service-key
PORT=3000
EOFBước 4: Supabase Client
typescript
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
const supabaseUrl = process.env.SUPABASE_URL!
const supabaseKey = process.env.SUPABASE_SERVICE_KEY!
export const supabase = createClient<Database>(supabaseUrl, supabaseKey)Phần 3: Schema Design (30 phút)
TeamFlow Database Schema
sql
-- Supabase SQL Editor → New Query
-- Users (extends Supabase auth.users)
CREATE TABLE profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
name TEXT NOT NULL CHECK (char_length(name) BETWEEN 1 AND 100),
role TEXT NOT NULL DEFAULT 'dev' CHECK (role IN ('admin','dev','qa','pm')),
avatar_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Projects
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL CHECK (char_length(name) BETWEEN 1 AND 200),
description TEXT,
color TEXT DEFAULT '#6366f1',
owner_id UUID NOT NULL REFERENCES profiles(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Tasks
CREATE TABLE tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL CHECK (char_length(title) BETWEEN 1 AND 500),
description TEXT,
status TEXT NOT NULL DEFAULT 'todo'
CHECK (status IN ('todo','in_progress','review','done')),
priority TEXT NOT NULL DEFAULT 'medium'
CHECK (priority IN ('low','medium','high','urgent')),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
assignee_id UUID REFERENCES profiles(id),
due_date TIMESTAMPTZ,
position INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Sprints
CREATE TABLE sprints (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
start_date TIMESTAMPTZ NOT NULL,
end_date TIMESTAMPTZ NOT NULL,
status TEXT DEFAULT 'planned' CHECK (status IN ('planned','active','completed')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Labels
CREATE TABLE labels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#6366f1',
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE
);
-- Task-Label junction
CREATE TABLE task_labels (
task_id UUID REFERENCES tasks(id) ON DELETE CASCADE,
label_id UUID REFERENCES labels(id) ON DELETE CASCADE,
PRIMARY KEY (task_id, label_id)
);
-- Row Level Security
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
-- Policies
CREATE POLICY "Users can view all profiles"
ON profiles FOR SELECT USING (true);
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE USING (auth.uid() = id);
CREATE POLICY "Authenticated users can view projects"
ON projects FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "Owners can manage projects"
ON projects FOR ALL USING (auth.uid() = owner_id);
CREATE POLICY "Team can view tasks"
ON tasks FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "Team can manage tasks"
ON tasks FOR ALL USING (auth.role() = 'authenticated');Lab: Run Schema
bash
antigravity "Connect to my Supabase project and verify the schema:
1. Check that profiles, projects, tasks, sprints, labels tables exist
2. Verify RLS policies are active
3. List all tables with column counts"Phần 4: Seed Data (15 phút)
sql
-- Insert Team Phoenix members (after they register via Auth)
-- This runs as migration seed
INSERT INTO profiles (id, name, role) VALUES
('00000000-0000-0000-0000-000000000001', 'Minh (CEO)', 'admin'),
('00000000-0000-0000-0000-000000000002', 'Lan (PM)', 'pm'),
('00000000-0000-0000-0000-000000000003', 'Đức (Sr Dev)', 'dev'),
('00000000-0000-0000-0000-000000000004', 'Hà (QA)', 'qa'),
('00000000-0000-0000-0000-000000000005', 'Tuấn (Jr Dev)', 'dev');
INSERT INTO projects (name, description, color, owner_id) VALUES
('TeamFlow MVP', 'AI-Native Project Management Platform', '#6366f1',
'00000000-0000-0000-0000-000000000002');Phần 5: Supabase Auth Preview (10 phút)
typescript
// Preview — will implement fully in Chapter 6
import { supabase } from './lib/supabase'
// Sign up
const { data, error } = await supabase.auth.signUp({
email: 'duc@phoenix.tech',
password: 'securePass123'
})
// Sign in
const { data: session } = await supabase.auth.signInWithPassword({
email: 'duc@phoenix.tech',
password: 'securePass123'
})
// Get user
const { data: { user } } = await supabase.auth.getUser()Quiz
Q1: Supabase dùng database engine gì?
- A) MongoDB
- B) PostgreSQL ✅
- C) MySQL
Q2: RLS (Row Level Security) giúp gì?
- A) Mã hóa data
- B) Kiểm soát ai xem/sửa data ở database level ✅
- C) Backup tự động
Homework
- [ ] Tạo Supabase project
- [ ] Run schema SQL
- [ ] Insert seed data
- [ ] Test query trong SQL Editor
Chương tiếp: Dự Án TeamFlow — Kiến Trúc & Khởi Tạo →