- 1. ภาพรวมสถาปัตยกรรม
- 2. สิ่งที่ต้องเตรียม
- 3. สร้างโปรเจกต์ Laravel
- 4. ตั้งค่าการเชื่อมต่อฐานข้อมูล
- 5. เลือก Schema ให้เหมาะสม
- 6. สร้าง Model และ Migration
- 7. สร้าง Controller และ API
- 8. Authentication: Laravel Auth หรือ Supabase Auth
- 9. ใช้ Supabase Data API จาก Laravel
- 10. ใช้ Supabase Storage กับ Laravel
- 11. Row Level Security (RLS)
- 12. แนวทาง Production ที่แนะนำ
- 13. ปัญหาที่พบบ่อย
- 14. สรุปการเลือกแนวทาง
- บทสรุป
- แหล่งอ้างอิง
Laravel กับ Supabase เป็นคู่ที่เหมาะสำหรับการสร้างเว็บแอปและ API ที่ต้องการพัฒนาเร็ว แต่ยังได้ความสามารถของ PostgreSQL, Authentication, Storage และระบบจัดการฐานข้อมูลแบบ Managed Service
บทความนี้อธิบายแนวทางที่ใช้งานได้จริงกับ Laravel รุ่นปัจจุบัน โดยเน้นการใช้ Supabase เป็น PostgreSQL หลักของแอป Laravel และสรุปทางเลือกสำหรับ Auth, Data API และ Storage
#1. ภาพรวมสถาปัตยกรรม
Laravel สามารถเชื่อมต่อ Supabase ได้ 2 แนวทางหลัก
- เชื่อมต่อ PostgreSQL โดยตรง ผ่าน PDO และใช้ Eloquent/Query Builder ซึ่งเหมาะกับแอป Laravel ทั่วไป
- เรียก Supabase Data API ผ่าน HTTP ซึ่งเหมาะกับกรณีที่ต้องการใช้ Row Level Security (RLS) และ API ของ Supabase โดยตรง
สำหรับระบบ Laravel ที่มี Controller, Model, Migration และ Business Logic อยู่แล้ว แนะนำให้เริ่มจากแนวทางที่ 1 เพราะเข้ากับโครงสร้าง Laravel มากที่สุด
ผู้ใช้
│
▼
Laravel Application
├── Routes / Controllers
├── Eloquent ORM
└── Laravel Auth หรือ Supabase Auth
│
▼
Supabase PostgreSQL
├── Tables / Indexes
├── Extensions
├── Row Level Security
└── Storage / Data API (เลือกใช้ตามความเหมาะสม)
#2. สิ่งที่ต้องเตรียม
- PHP และ Composer
- Laravel
- PostgreSQL driver สำหรับ PHP (
pdo_pgsql) - บัญชี Supabase และ Project
- Database password ของ Supabase
- Connection string จากเมนู Connect ใน Supabase Dashboard
ตรวจสอบ PHP extension ได้ด้วยคำสั่งต่อไปนี้
php -m | grep -E "pdo_pgsql|pgsql"
ถ้ายังไม่มี pdo_pgsql ให้ติดตั้งตามระบบปฏิบัติการ เช่น Ubuntu
sudo apt update
sudo apt install php-pgsql
#3. สร้างโปรเจกต์ Laravel
composer create-project laravel/laravel laravel-supabase
cd laravel-supabase
php artisan key:generate
หากต้องการระบบ Login/Register แบบพื้นฐาน สามารถติดตั้ง Laravel Breeze ได้
composer require laravel/breeze --dev
php artisan breeze:install blade
สามารถเปลี่ยน blade เป็น react หรือ vue ได้ตาม Stack ที่เลือกใช้
#4. ตั้งค่าการเชื่อมต่อฐานข้อมูล
จาก Supabase Dashboard ให้เปิด Connect แล้วเลือก Connection String แบบ Session Pooler หรือ Direct Connection ตามสภาพแวดล้อมของระบบ
ตัวอย่างไฟล์ .env
APP_NAME=LaravelSupabase
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000
DB_CONNECTION=pgsql
DB_URL=postgres://postgres.PROJECT_REF:YOUR_PASSWORD@POOLER_HOST:5432/postgres
ถ้ารหัสผ่านมีอักขระพิเศษ เช่น &, #, ?, / หรือช่องว่าง ต้องทำ URL encoding ก่อนนำไปใส่ใน DB_URL
จากนั้นแก้ config/database.php ในส่วน pgsql
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'search_path' => env('DB_SCHEMA', 'public'),
'sslmode' => env('DB_SSLMODE', 'require'),
],
ล้าง Configuration Cache แล้วทดสอบการเชื่อมต่อ
php artisan config:clear
php artisan migrate
php artisan db:show
#ข้อควรระวังเรื่อง Pooler
ไม่ควรนำ Transaction Pooler พอร์ต 6543 มาใช้เป็นแหล่งข้อมูลหลักของ ORM โดยไม่ตรวจสอบข้อจำกัดของ prepared statements ก่อน สำหรับ Laravel และ Eloquent ให้เริ่มจาก Session Pooler พอร์ต 5432 หรือ Direct Connection ตามคำแนะนำใน Supabase Dashboard
สำหรับ Production ควรเก็บ Connection String ใน Secret หรือ Environment Variable ของแพลตฟอร์มที่ใช้ Deploy และไม่ Commit ไฟล์ .env เข้า Git
#5. เลือก Schema ให้เหมาะสม
Supabase เปิด public schema ให้ใช้งานผ่าน Data API ได้ ดังนั้นระบบที่ใช้ Laravel เชื่อมต่อโดยตรงอาจแยกตารางของแอปไปอยู่ใน schema เช่น laravel เพื่อควบคุมขอบเขตให้ชัดเจน
สร้าง Schema จาก SQL Editor ของ Supabase
create schema if not exists laravel;
แล้วกำหนดใน .env
DB_SCHEMA=laravel
ถ้าใช้ public schema และต้องการเปิด Data API/RLS ให้ทบทวนสิทธิ์ของแต่ละตารางอย่างละเอียดก่อนนำขึ้น Production
#6. สร้าง Model และ Migration
ตัวอย่างระบบจัดการงาน (Tasks)
php artisan make:model Task -m
แก้ Migration ใน database/migrations/..._create_tasks_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description')->nullable();
$table->boolean('completed')->default(false);
$table->timestampsTz();
});
}
public function down(): void
{
Schema::dropIfExists('tasks');
}
};
แก้ Model ใน app/Models/Task.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = [
'title',
'description',
'completed',
];
protected function casts(): array
{
return [
'completed' => 'boolean',
];
}
}
รัน Migration
php artisan migrate
#7. สร้าง Controller และ API
php artisan make:controller Api/TaskController --api
ตัวอย่าง Controller แบบย่อ
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
return Task::query()
->latest()
->paginate(20);
}
public function store(Request $request)
{
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
]);
return response()->json(
Task::create($data),
201
);
}
public function show(Task $task)
{
return $task;
}
public function update(Request $request, Task $task)
{
$data = $request->validate([
'title' => ['sometimes', 'required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'completed' => ['sometimes', 'boolean'],
]);
$task->update($data);
return $task->refresh();
}
public function destroy(Task $task)
{
$task->delete();
return response()->noContent();
}
}
กำหนด Route ใน routes/api.php
use App\Http\Controllers\Api\TaskController;
use Illuminate\Support\Facades\Route;
Route::apiResource('tasks', TaskController::class);
ทดสอบด้วยคำสั่ง
php artisan serve
curl http://127.0.0.1:8000/api/tasks
#8. Authentication: Laravel Auth หรือ Supabase Auth
#ทางเลือก A: ใช้ Laravel Auth
เหมาะกับระบบที่ใช้ Laravel เป็นศูนย์กลางอยู่แล้ว เช่น Breeze, Fortify, Sanctum และ Session Cookie
- ตารางผู้ใช้เก็บใน
usersของ Laravel - Login/Register จัดการด้วย Laravel
- API ใช้ Laravel Sanctum ได้
- Supabase ทำหน้าที่เป็น PostgreSQL และ Storage ตามที่เลือกใช้
ข้อดีคือโค้ด Laravel เป็นแนวทางมาตรฐานและดูแลต่อได้ง่าย โดย Quickstart ของ Supabase สำหรับ Laravel ก็ใช้ Laravel Authentication เป็นค่าเริ่มต้น
#ทางเลือก B: ใช้ Supabase Auth
เหมาะกับกรณีที่มี Frontend หรือ Mobile App หลายตัว และต้องการให้ Supabase เป็นศูนย์กลางของ Identity
แนวทางที่ควรวางแผนให้ชัดเจน
- Frontend Login ผ่าน Supabase Auth
- Frontend ได้ Access Token แบบ JWT
- ส่ง JWT มาที่ Laravel API ผ่าน
Authorization: Bearer ... - Laravel ตรวจสอบ Token และกำหนดสิทธิ์การใช้งาน
- PostgreSQL/RLS ใช้
auth.uid()ได้เมื่อคำขอเข้าสู่ Data API ตามรูปแบบที่ Supabase รองรับ
ไม่ควรมีทั้ง Laravel users และ Supabase Auth users โดยไม่มีการกำหนดแหล่งข้อมูลหลัก เพราะอาจทำให้ข้อมูลผู้ใช้และสิทธิ์ไม่ตรงกัน
#9. ใช้ Supabase Data API จาก Laravel
ถ้าต้องการเรียก Data API สามารถใช้ Laravel HTTP Client ได้ โดยเก็บ URL และ Key ไว้ใน .env
SUPABASE_URL=https://PROJECT_REF.supabase.co
SUPABASE_ANON_KEY=your-anon-key
ตัวอย่าง Service
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class SupabaseDataService
{
public function listTasks(): array
{
return Http::withHeaders([
'apikey' => config('services.supabase.anon_key'),
'Authorization' => 'Bearer '.config('services.supabase.anon_key'),
])
->get(config('services.supabase.url').'/rest/v1/tasks', [
'select' => '*',
'order' => 'created_at.desc',
])
->throw()
->json();
}
}
เพิ่มใน config/services.php
'supabase' => [
'url' => env('SUPABASE_URL'),
'anon_key' => env('SUPABASE_ANON_KEY'),
],
anon key ไม่ใช่สิทธิ์ที่ทำให้ข้าม RLS ได้ ดังนั้นต้องเขียน Policy ให้ถูกต้อง ส่วน service_role key มีสิทธิ์สูงและต้องใช้เฉพาะฝั่ง Server เท่านั้น ห้ามส่งไปยัง Browser, Mobile App หรือเก็บไว้ในโค้ดฝั่ง Client
#10. ใช้ Supabase Storage กับ Laravel
มี 2 แนวทางทั่วไป
#อัปโหลดผ่าน Laravel Filesystem
เหมาะเมื่อ Laravel เป็นผู้ควบคุมการอัปโหลดและต้องการบันทึก Metadata ในฐานข้อมูล Laravel
$path = $request->file('avatar')->store('avatars', 's3');
Supabase Storage รองรับ S3-compatible API ในกรณีที่ต้องการเชื่อมผ่าน S3 adapter ควรตรวจสอบ Endpoint, Region, Access Key และ Secret Key จาก Dashboard ให้ตรงกับรูปแบบที่ Supabase กำหนด
#เรียก Supabase Storage API
เหมาะเมื่อใช้ระบบ Bucket และ Policy ของ Supabase เป็นหลัก ควรแยก Service สำหรับการ Upload, Download และ Signed URL ไม่ควรปนกับ Controller โดยตรง
ไม่ว่าจะเลือกวิธีใด ควรตรวจสอบชนิดไฟล์, ขนาดไฟล์, ชื่อไฟล์, สิทธิ์การเข้าถึง และการป้องกัน Path Traversal
#11. Row Level Security (RLS)
RLS เป็นหัวใจสำคัญเมื่อเปิดให้ Client เรียก Supabase Data API โดยตรง ตัวอย่าง Policy ต้องออกแบบให้สัมพันธ์กับข้อมูลผู้ใช้ เช่น มีคอลัมน์ user_id ในตาราง
alter table public.tasks enable row level security;
create policy "Users can read own tasks"
on public.tasks
for select
to authenticated
using (user_id = auth.uid());
create policy "Users can insert own tasks"
on public.tasks
for insert
to authenticated
with check (user_id = auth.uid());
ถ้า Laravel เชื่อมต่อฐานข้อมูลโดยตรงด้วย Database Password การควบคุมสิทธิ์จะอยู่ที่ Laravel และ PostgreSQL Role เป็นหลัก ไม่ควรคิดว่า RLS จะป้องกันทุกกรณีโดยอัตโนมัติ ให้ทดสอบสิทธิ์ทั้งจาก Laravel และจาก Data API แยกกัน
#12. แนวทาง Production ที่แนะนำ
- บังคับ
sslmode=require - ใช้ Secret Manager หรือ Environment Variables
- ไม่ Commit
DB_URL, Database Password หรือservice_role key - ใช้ Migration ใน CI/CD แทนการแก้ Schema ด้วยมือ
- แยก
publicschema กับ schema ภายในแอปเมื่อเหมาะสม - สร้าง Index สำหรับคอลัมน์ที่ใช้ค้นหาและ Foreign Key
- ใช้ Pagination แทนการดึงข้อมูลทั้งหมด
- ตรวจสอบ Query ด้วย Laravel Telescope, Log และ Database Monitoring
- ตั้งค่า Backup และ Point-in-Time Recovery ตามระดับความสำคัญของระบบ
- ทดสอบ RLS ด้วยผู้ใช้หลายบทบาท
- จำกัด CORS และตรวจสอบ Token ก่อนเข้าถึง API
- อย่าใช้
service_role keyใน JavaScript ที่ส่งให้ผู้ใช้
#13. ปัญหาที่พบบ่อย
#could not find driver
แปลว่ายังไม่มี pdo_pgsql
php -m | grep pgsql
#password authentication failed
ตรวจสอบ Password, Project Reference, Username และ Host จาก Connection String ใหม่ อย่าพิมพ์ Host เองจากความจำ
#ต่อฐานข้อมูลได้ แต่ Migration ล้มเหลว
ตรวจสอบว่าใช้ Pooler แบบใด หากใช้ Transaction Pooler ให้เปลี่ยนเป็น Session Pooler/Direct Connection สำหรับงานที่ต้องใช้ prepared statements หรือ schema operations
#relation does not exist
ตรวจสอบ search_path, ชื่อ Schema, ชื่อตาราง และ Migration ที่รันไปแล้ว
php artisan migrate:status
php artisan config:clear
#Data API ได้ 401 หรือ 403
ตรวจสอบ API Key, Bearer Token, RLS Policy, Role (anon หรือ authenticated) และชื่อ Schema ที่เปิดให้ Data API เข้าถึง
#14. สรุปการเลือกแนวทาง
| ความต้องการ | แนวทางที่เหมาะสม |
|---|---|
| Laravel CRUD ทั่วไป | PostgreSQL ผ่าน Eloquent |
| Laravel เป็นศูนย์กลาง Auth | Laravel Breeze/Fortify/Sanctum |
| Frontend หลายแพลตฟอร์มใช้ Identity ร่วมกัน | Supabase Auth |
| ต้องการ RLS ที่ Data API | Supabase Data API + Policy |
| งาน Migration/ORM จำนวนมาก | Session Pooler หรือ Direct Connection |
| อัปโหลดไฟล์แบบควบคุมจาก Laravel | Laravel Filesystem หรือ Supabase Storage API |
| Production ที่ปลอดภัย | SSL + Secret Management + RLS/Authorization ที่ทดสอบแล้ว |
#บทสรุป
รูปแบบที่เริ่มต้นได้ง่ายและเหมาะกับแอป Laravel ส่วนใหญ่คือ ใช้ Supabase เป็น PostgreSQL แล้วเชื่อมต่อด้วย Eloquent ผ่าน Session Pooler หรือ Direct Connection พร้อมเปิด SSL จากนั้นใช้ Laravel Auth เป็นตัวจัดการผู้ใช้ และค่อยเพิ่ม Supabase Storage หรือ Data API เมื่อมีเหตุผลด้านสถาปัตยกรรม
ถ้าระบบมี Frontend หรือ Mobile หลายแพลตฟอร์มและต้องการระบบ Identity กลาง จึงค่อยพิจารณา Supabase Auth โดยกำหนดให้ชัดว่า Token, User และสิทธิ์ถูกตรวจสอบที่ชั้นใด