สัปดาห์ที่ 6

Supabase: ออกแบบฐานข้อมูล PostgreSQL & SQL เบื้องต้น

CLO2 CLO3
📖 ทฤษฎี

1. Supabase คืออะไร

Supabase คือ Open-Source Backend-as-a-Service ที่รวม PostgreSQL, Auth, Storage, Realtime, และ Edge Functions ไว้ใน Platform เดียว บางครั้งเรียกว่า "Firebase Alternative" เพราะให้บริการคล้ายกันแต่สร้างบน PostgreSQL แทน NoSQL

สิ่งที่ทำให้ Supabase โดดเด่น:

  • PostgreSQL — ฐานข้อมูลเชิงสัมพันธ์ที่ทรงพลัง รองรับ SQL มาตรฐาน, JSON, Full-Text Search, และ Extension หลายร้อยตัว
  • Auth — ระบบ Authentication พร้อมใช้ รองรับ Email/Password, OAuth (Google, GitHub), Magic Link โดยไม่ต้องเขียนโค้ดใหม่
  • Storage — อัปโหลดและจัดการไฟล์ รูปภาพ และวิดีโอ มี CDN ในตัว
  • Realtime — Subscribe การเปลี่ยนแปลงข้อมูลใน Database แบบ Real-time ผ่าน WebSocket
  • Auto-generated API — สร้าง REST API และ GraphQL API จากโครงสร้างตารางอัตโนมัติ พร้อม SDK สำหรับ JavaScript, Python, Flutter, Swift
สถาปัตยกรรม Supabase:

  ┌─────────────────────────────────────────────┐
  │              Supabase Platform              │
  │                                             │
  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
  │  │PostgreSQL│  │   Auth   │  │ Storage  │  │
  │  └──────────┘  └──────────┘  └──────────┘  │
  │                                             │
  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
  │  │ Realtime │  │PostgREST │  │  Edge Fn │  │
  │  └──────────┘  └──────────┘  └──────────┘  │
  └─────────────────────────────────────────────┘
               │
         supabase-js SDK
               │
  ┌────────────────────────────────┐
  │   Frontend (React / Vanilla)   │
  └────────────────────────────────┘
          

2. การสร้างโปรเจกต์ใน Supabase Dashboard

การเริ่มต้นใช้ Supabase ทำได้ผ่าน Dashboard ที่ supabase.com โดยไม่จำเป็นต้องติดตั้งอะไรบน Machine ของตัวเอง เพราะฐานข้อมูลและ API รันบน Cloud ทั้งหมด

ขั้นตอนการสร้างโปรเจกต์ใหม่:

  • สมัครบัญชีที่ supabase.com (ใช้ GitHub Account ได้) แล้วคลิก New Project
  • ตั้งชื่อโปรเจกต์ กำหนด Database Password ที่แข็งแกร่ง และเลือก Region (แนะนำ Southeast Asia (Singapore) สำหรับผู้ใช้ในไทย)
  • รอประมาณ 1–2 นาทีให้ Supabase สร้าง PostgreSQL Instance ให้
  • ไปที่ Settings → API บันทึก Project URL และ anon key ไว้ใช้ในโค้ด JavaScript
เคล็ดลับ — Free Tier

Supabase Free Tier ให้ 2 โปรเจกต์ฟรี, Storage 500 MB, และ Database 500 MB เพียงพอสำหรับโปรเจกต์ในคอร์สนี้ หากโปรเจกต์ไม่มีการใช้งานเกิน 7 วัน จะถูก Pause อัตโนมัติ สามารถ Resume ได้ผ่าน Dashboard

3. PostgreSQL พื้นฐาน — ความแตกต่างจาก MySQL

PostgreSQL เป็น Relational Database ที่มีความสามารถสูงกว่า MySQL หลายด้าน Supabase เลือก PostgreSQL เพราะรองรับ Feature ขั้นสูงที่จำเป็นสำหรับ Modern Web Application

ความแตกต่างสำคัญที่ควรทราบ:

  • UUID — PostgreSQL ใช้ UUID เป็น Primary Key แทน INT AUTO_INCREMENT ของ MySQL UUID สร้างด้วย gen_random_uuid() ทำให้ ID ไม่เดาได้และ Merge ข้อมูลจากหลาย Database ได้
  • TIMESTAMPTZ — PostgreSQL ใช้ TIMESTAMPTZ (Timestamp with Time Zone) แทน TIMESTAMP ของ MySQL เพื่อให้เก็บ Timezone ไว้ด้วยเสมอ
  • CHECK Constraint — PostgreSQL รองรับ CHECK Constraint ที่ซับซ้อนโดยตรงใน Column Definition เช่น CHECK (status IN ('pending','confirmed','cancelled'))
  • Extensions — PostgreSQL มี Extension เสริมเช่น uuid-ossp, pgvector (AI/Vector Search), postgis (GIS/Location Data)
  • Row Level Security (RLS) — PostgreSQL รองรับ Policy ความปลอดภัยระดับแถว ซึ่งเป็นหัวใจสำคัญของ Supabase ในการจำกัดการเข้าถึงข้อมูล
เปรียบเทียบ MySQL กับ PostgreSQL ใน Supabase:

  Feature          MySQL                PostgreSQL (Supabase)
  ──────────────────────────────────────────────────────────
  Primary Key      INT AUTO_INCREMENT   UUID DEFAULT gen_random_uuid()
  Timestamp        TIMESTAMP            TIMESTAMPTZ
  String           VARCHAR(255)         TEXT (ไม่จำกัดขนาด)
  Boolean          TINYINT(1)           BOOLEAN
  JSON             JSON                 JSONB (indexed, ค้นหาได้เร็วกว่า)
  Enum             ENUM('a','b')        CHECK (col IN ('a','b'))
  Auth Integration ไม่มี               auth.users (built-in)
          

4. ออกแบบตาราง BookEasy: services, bookings, profiles

ระบบ BookEasy ต้องการตาราง 3 ตาราง ที่มีความสัมพันธ์กันผ่าน Foreign Key:

  • services — เก็บข้อมูลบริการที่ให้จอง เช่น ตัดผม นวด สปา มี id, name, description, price, duration_min, image_url, created_at
  • bookings — เก็บข้อมูลการจอง เชื่อมกับ auth.users และ services ผ่าน Foreign Key มี id, user_id, service_id, booking_date, booking_time, status, notes, created_at
  • profiles — เก็บข้อมูลโปรไฟล์ผู้ใช้ที่เพิ่มเติมจาก auth.users เชื่อมกับ auth.users แบบ One-to-One มี id, full_name, phone, avatar_url, updated_at

ความสัมพันธ์ระหว่างตาราง: ผู้ใช้ 1 คน (auth.users) มีได้ 1 โปรไฟล์ (profiles) และมีการจองได้หลายครั้ง (bookings) แต่ละการจองเชื่อมกับบริการ 1 รายการ (services)

ER Diagram — BookEasy:

  auth.users (Supabase built-in)
       │ id (UUID)
       │
       ├──────────────────────────────┐
       │                              │
       ▼ (1:1)                        ▼ (1:N)
  profiles                       bookings
  ─────────────────               ──────────────────────────
  id → auth.users.id              id (UUID, PK)
  full_name                       user_id → auth.users.id
  phone                           service_id → services.id
  avatar_url                      booking_date
  updated_at                      booking_time
                                  status (pending/confirmed/cancelled)
                                  notes
                                  created_at
                                       │
                                       │ (N:1)
                                       ▼
                                  services
                                  ─────────────────
                                  id (UUID, PK)
                                  name
                                  description
                                  price
                                  duration_min
                                  image_url
                                  created_at
          

5. Row Level Security (RLS) — แนวคิดพื้นฐาน

Row Level Security (RLS) คือ Feature ของ PostgreSQL ที่ให้กำหนด Policy ว่าผู้ใช้แต่ละคนสามารถ SELECT, INSERT, UPDATE, หรือ DELETE แถวไหนได้บ้าง โดยตรวจสอบที่ระดับ Database ไม่ใช่ระดับ Application Code

ทำไม RLS ถึงสำคัญใน Supabase:

  • ความปลอดภัยโดย Default — เมื่อเปิด RLS บนตาราง ไม่มีใครเข้าถึงข้อมูลได้เลยจนกว่าจะสร้าง Policy ป้องกันการรั่วไหลข้อมูลหาก Developer ลืมเพิ่ม Filter ใน Query
  • ตรวจสอบที่ Database — แม้ผู้ไม่หวังดีจะ Bypass Application Code ได้ RLS ยังคุ้มครองข้อมูลที่ระดับ Database อยู่
  • ใช้ JWT Token — Supabase ส่ง auth.uid() ซึ่งคือ UUID ของผู้ใช้ปัจจุบัน ไปใน Policy เพื่อเปรียบเทียบ เช่น user_id = auth.uid() ให้ผู้ใช้เห็นเฉพาะข้อมูลของตัวเอง
ข้อควรระวัง — RLS ใน Lab นี้

ใน Lab 5 นี้ยังไม่เปิด RLS เพื่อให้ทดสอบได้ง่าย แต่ใน Production จำเป็นต้องเปิด RLS ทุกตาราง และเขียน Policy อย่างรอบคอบ มิเช่นนั้นข้อมูลของผู้ใช้ทุกคนจะมองเห็นกันได้

💻 โค้ดตัวอย่าง
Supabase SQL Editor SQL
-- snippet 1: สร้างตาราง BookEasy ใน Supabase SQL Editor

-- ตารางบริการ
CREATE TABLE services (
  id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name       TEXT NOT NULL,
  description TEXT,
  price      NUMERIC(10,2) NOT NULL,
  duration_min INT NOT NULL,
  image_url  TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- ตารางการจอง
CREATE TABLE bookings (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id      UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  service_id   UUID REFERENCES services(id) ON DELETE CASCADE,
  booking_date DATE NOT NULL,
  booking_time TIME NOT NULL,
  status       TEXT DEFAULT 'pending' CHECK (status IN ('pending','confirmed','cancelled')),
  notes        TEXT,
  created_at   TIMESTAMPTZ DEFAULT NOW()
);

-- ตารางโปรไฟล์ผู้ใช้
CREATE TABLE profiles (
  id         UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  full_name  TEXT,
  phone      TEXT,
  avatar_url TEXT,
  updated_at TIMESTAMPTZ DEFAULT NOW()
);
Supabase SQL Editor SQL
-- snippet 2: เพิ่มข้อมูลทดสอบ
INSERT INTO services (name, description, price, duration_min) VALUES
  ('ตัดผมชาย', 'ตัดผมสไตล์เกาหลี/ยุโรป พร้อมล้างและเป่า', 150, 45),
  ('ตัดผมหญิง', 'ตัดและจัดทรงผมสำหรับผู้หญิง', 250, 60),
  ('นวดแผนไทย', 'นวดผ่อนคลายกล้ามเนื้อด้วยภูมิปัญญาไทย', 400, 60),
  ('นวดหน้า', 'บำรุงผิวหน้า ลดรอยแดง เพิ่มความชุ่มชื้น', 350, 45),
  ('สปาหน้า', 'โปรแกรมดูแลผิวหน้าครบวงจร', 600, 90);

-- ดูข้อมูล
SELECT id, name, price, duration_min FROM services ORDER BY price;
src/lib/supabase.js JavaScript
// snippet 3: เชื่อมต่อ Supabase ใน JavaScript
// ติดตั้ง: npm install @supabase/supabase-js

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

const supabaseUrl = 'https://xxxx.supabase.co';      // จาก Supabase Dashboard
const supabaseKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI...'; // anon key

export const supabase = createClient(supabaseUrl, supabaseKey);

// ทดสอบดึงข้อมูล
const { data, error } = await supabase
  .from('services')
  .select('*')
  .order('price', { ascending: true });

console.log(data, error);

🧪 ปฏิบัติการ Lab 5 — BookEasy: สร้างฐานข้อมูลใน Supabase

ฝึกสร้างฐานข้อมูล PostgreSQL ใน Supabase Dashboard เขียน SQL สร้างตารางและเพิ่มข้อมูลทดสอบ จากนั้นเชื่อมต่อผ่าน JavaScript Client

ต่อยอดจาก Lab 4

ต่อยอดจาก Lab 4 — สร้าง database จริงใน Supabase เพื่อใช้แทน mock data ใน Week 7

1
สร้างโปรเจกต์ Supabase

สร้างโปรเจกต์ใหม่ใน Supabase Dashboard และบันทึก Credentials ที่จำเป็น

  • ไปที่ supabase.com → Sign In ด้วย GitHub Account → คลิก New Project
  • ตั้งชื่อโปรเจกต์ว่า bookeasy กำหนด Database Password และเลือก Region Southeast Asia (Singapore)
  • รอให้โปรเจกต์พร้อม (ประมาณ 1–2 นาที) จนสถานะเปลี่ยนเป็น "Active"
  • ไปที่ Settings → API และบันทึก Project URL และ anon public key ไว้ใช้ใน Task 4
เกณฑ์การผ่าน

โปรเจกต์ Supabase ชื่อ "bookeasy" สถานะ Active ใน Dashboard มี Project URL และ anon key พร้อมใช้งาน ถ่ายภาพหน้าจอ Settings → API ที่แสดง URL และ Key

คำแนะนำ

Database Password ต้องจำหรือบันทึกไว้ในที่ปลอดภัยเพราะจะใช้หาก Connect ตรงผ่าน psql แต่สำหรับ JavaScript SDK ใช้ anon key แทน ไม่จำเป็นต้องใช้ Password

2
สร้างตาราง

ใช้ SQL Editor ใน Supabase Dashboard สร้างตาราง 3 ตารางสำหรับ BookEasy

  • ไปที่เมนู SQL Editor ใน Supabase Dashboard แล้วคลิก New Query
  • คัดลอก SQL จาก Snippet 1 ด้านบน วางในช่อง Editor แล้วคลิก Run
  • ตรวจสอบว่า Query สำเร็จ (ไม่มี Error Message สีแดง)
  • ไปที่เมนู Table Editor ตรวจว่ามีตารางครบ 3 ตาราง: services, bookings, profiles
เกณฑ์การผ่าน

Table Editor แสดงตาราง 3 ตารางครบ (services, bookings, profiles) แต่ละตารางมี Column ครบตามที่ออกแบบ ถ่ายภาพหน้าจอ Table Editor ที่แสดงทั้ง 3 ตาราง

คำแนะนำ

หากเกิด Error "relation auth.users does not exist" ให้ตรวจสอบว่ารัน SQL ใน Project ที่ถูกต้อง auth.users เป็นตาราง Built-in ของ Supabase ที่มีอยู่แล้วในทุกโปรเจกต์ ไม่ต้องสร้างเอง

3
เพิ่มข้อมูลทดสอบ

เพิ่มข้อมูลบริการ 5 รายการลงในตาราง services และตรวจสอบด้วย SELECT

  • เปิด SQL Editor → New Query อีกครั้ง
  • คัดลอก SQL จาก Snippet 2 ด้านบน (ส่วน INSERT) วางในช่อง Editor แล้วคลิก Run
  • รัน SELECT * FROM services; ในช่อง Editor เพื่อตรวจสอบ
  • ตรวจว่าผลลัพธ์แสดง 5 แถว พร้อม UUID ที่ระบบสร้างให้อัตโนมัติ
เกณฑ์การผ่าน

SELECT * FROM services; แสดงผลลัพธ์ 5 แถว ทุกแถวมี id เป็น UUID และ created_at ที่ระบบกำหนดให้อัตโนมัติ ถ่ายภาพหน้าจอผลลัพธ์ของ SELECT

คำแนะนำ

สามารถดูข้อมูลผ่าน Table Editor ได้เช่นกัน โดยคลิกที่ตาราง services เพื่อดูในรูปแบบตาราง Excel ที่อ่านง่ายกว่า SQL Query Result

4
ทดสอบ Supabase Client ใน project

ติดตั้ง Supabase JavaScript SDK ใน bookeasy-api และทดสอบดึงข้อมูลจากตาราง services

  • เปิด Terminal ใน folder bookeasy-api แล้วรัน npm install @supabase/supabase-js
  • สร้างไฟล์ใหม่ src/lib/supabase.js แล้วคัดลอกโค้ดจาก Snippet 3 ด้านบน
  • แทนค่า supabaseUrl และ supabaseKey ด้วย URL และ anon key ของโปรเจกต์ตัวเอง (จาก Task 1)
  • สร้างไฟล์ test-db.js ที่ import supabase จาก ./src/lib/supabase.js และรัน query ดึงข้อมูลจาก services
  • รัน node test-db.js แล้วตรวจว่า data แสดง Array 5 รายการ และ error เป็น null
เกณฑ์การผ่าน

รัน node test-db.js แล้ว Console แสดง Array ของบริการ 5 รายการ และ error: null ถ่ายภาพหน้าจอ Terminal ที่แสดงผลลัพธ์จาก Supabase

คำแนะนำ

หาก Error "invalid API key" ให้ตรวจสอบว่าใช้ anon public key ไม่ใช่ service_role key (อย่าใช้ service_role key ใน Frontend หรือ Client-side code) Key ที่ถูกต้องจะขึ้นต้นด้วย eyJ และยาวมาก