مقدمة في Supabase — Supabase
مقدمة في Supabase
Supabase منصة "خلفية جاهزة" مبنية على PostgreSQL: قاعدة بيانات حقيقية، و API يُولَّد تلقائياً من جداولك، ومصادقة، وتخزين ملفات، ووقت حقيقي، ودوال خادم (Edge Functions). كل ما تعلمته في دورة SQL ينطبق هنا مباشرة.
العميل
// npm install @supabase/supabase-js
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
);أنشئ عميلاً واحداً وصدّره، واستورده في بقية الملفات.
مفتاحان مختلفان جداً
| المفتاح | أين | الصلاحية |
|---|---|---|
| anon / publishable | في الواجهة (علني بطبيعته) | ما تسمح به سياسات RLS فقط |
| service_role / secret | الخادم فقط (Edge Functions، خادمك) | يتجاوز RLS كاملاً |
مفتاح anon يُرى في كود الواجهة ولا مشكلة في ذلك — بشرط تفعيل RLS على كل جدول (الدرس التالي). أما service_role في الواجهة فيعني أن أي زائر يملك قاعدة بياناتك كلها.
الاستعلامات
كل استدعاء يعيد { data, error } — لا يرمي استثناء، فافحص error دائماً:
import { supabase } from "./supabaseClient.js";
export async function listRecentProducts() {
const { data, error } = await supabase
.from("products")
.select("id, name, price")
.eq("in_stock", true)
.order("created_at", { ascending: false })
.limit(20);
if (error) throw error;
return data;
}| العملية | الدالة |
|---|---|
| قراءة | .select("أعمدة") مع فلاتر .eq .gt .ilike .in... |
| إضافة | .insert({...}).select() — بدون .select() لا تُعاد الصفوف المضافة |
| تعديل | .update({...}).eq("id", id) |
| حذف | .delete().eq("id", id) |
| إضافة أو تعديل | .upsert({...}) |
⚠️ update و delete بلا فلتر: supabase-js يرفضها افتراضياً لحمايتك، لكن انتبه دائماً للشرط — والصفوف التي لا تسمح RLS بتعديلها تُتجاهل بصمت (لا خطأ، 0 صفوف).
الأنواع مع TypeScript
npx supabase gen types typescript يولّد أنواعاً من مخطط قاعدتك، فيعرف المحرر أسماء الأعمدة وأنواعها ويكتشف الأخطاء قبل التشغيل.
تمرين
أنشئ مشروع Supabase مجانياً، وجدول todos (id، title، done، created_at)، ثم اكتب دوال listTodos و addTodo و toggleTodo و deleteTodo تفحص error في كل منها.
// supabaseClient.js — عميل واحد للتطبيق كله
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY // مفتاح anon فقط في الواجهة — لا service_role أبداً
);
// todos.js — كل عملية في دالتها، وكل نتيجة تُفحص
export async function listTodos() {
const { data, error } = await supabase
.from("todos")
.select("id, title, done, created_at")
.order("created_at", { ascending: false });
if (error) throw error;
return data;
}
export async function addTodo(title) {
const { data, error } = await supabase
.from("todos")
.insert({ title: title.trim() })
.select() // بدونها لا يُعاد الصف المضاف
.single();
if (error) throw error;
return data;
}
export async function toggleTodo(id, done) {
const { data, error } = await supabase.from("todos").update({ done }).eq("id", id).select();
if (error) throw error;
if (data.length === 0) throw new Error("لم يُعدَّل شيء — غير موجود أو تمنعه سياسات RLS");
return data[0];
}
export async function deleteTodo(id) {
const { error } = await supabase.from("todos").delete().eq("id", id);
if (error) throw error;
}اختبر فهمك