Combining REST, Realtime, and Storage
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”feature จริงแทบไม่เคยแตะแค่ service เดียว — upload รูป บันทึก row ที่ reference รูปนั้น แล้วให้ client อื่นเห็นรูปนั้นโผล่ขึ้นมาแบบ live คือ flow เดียวที่ compose Storage, REST API ที่ป้องกันด้วย RLS และ Realtime เข้าด้วยกัน และแต่ละส่วนก็คือสิ่งที่เรียนมาแล้วในบทก่อนหน้า
Step 1: upload file ขึ้น Storage
หัวข้อที่มีชื่อว่า “Step 1: upload file ขึ้น Storage”นี่คือ call supabase.storage.from(bucket).upload() ตัวเดียวกับที่เจอในครึ่ง Storage ของ module Realtime and Storage ฟังก์ชันนี้ส่ง bytes ของ file ไปที่ bucket และถ้าสำเร็จจะคืน path ที่ file ถูกเก็บไว้
const file = fileInput.files[0];const filePath = `${userId}/${crypto.randomUUID()}-${file.name}`;
const { data: uploadData, error: uploadError } = await supabase.storage .from('photos') .upload(filePath, file);
if (uploadError) { console.error('upload failed:', uploadError.message); return;}การเช็ก error ตรงนี้สำคัญเหมือนในบทก่อน network สะดุดหรือ policy ของ Storage ปฏิเสธ upload ก็โผล่มาผ่าน uploadError เหมือนกัน ไม่ใช่ exception ที่ถูก throw และถ้าข้ามการเช็กนี้ไป ก็เท่ากับพยายาม insert row ที่ชี้ไปยัง file ที่ไม่เคยถูกบันทึกจริงแบบเงียบ ๆ
Step 2: บันทึก row ที่ reference file นั้น โดยป้องกันด้วย RLS
หัวข้อที่มีชื่อว่า “Step 2: บันทึก row ที่ reference file นั้น โดยป้องกันด้วย RLS”พอ file ถูกเก็บไว้แล้ว app จะบันทึก row ใน posts ที่ชี้ไปยัง file นั้น นี่คือ call .insert() ธรรมดา แต่สำเร็จได้เพราะ row level security policy จาก module Auth เท่านั้น — policy ที่ให้ user insert row ได้ก็ต่อเมื่อ user_id ตรงกับ auth.uid() ฝั่ง client ไม่ต้องทำอะไรเพิ่มเพื่อให้ได้การป้องกันนั้น เพราะ Postgres บังคับใช้ให้อัตโนมัติ สำหรับทุก request ที่ client ส่งไป
const { data: post, error: insertError } = await supabase .from('posts') .insert({ user_id: userId, photo_path: uploadData.path, }) .select() .single();
if (insertError) { console.error('insert failed:', insertError.message); return;}ถ้า userId ไม่ตรงกับ auth.uid() ของ user ที่ signed in อยู่ RLS จะปฏิเสธ insert และ insertError จะมีค่า — { data, error } pattern เดียวกันใช้ได้ตรงนี้เหมือนกับ upload และกฎเดียวกันจากบทก่อนก็ใช้ได้เช่นกัน branch ตาม insertError.code ถ้าต้องแยกว่าเป็นการถูก policy ปฏิเสธหรือเป็นอย่างอื่น แทนที่จะ parse ข้อความ message
Step 3: client อีกตัวเห็น row ใหม่แบบ live
หัวข้อที่มีชื่อว่า “Step 3: client อีกตัวเห็น row ใหม่แบบ live”client ตัวที่สองที่ต่ออยู่แล้ว — คนอื่นที่กำลังดู feed เดียวกัน — subscribe อยู่กับ postgres_changes บน table posts ตามที่ module Realtime อธิบายไว้ ทันทีที่ insert จาก step 2 commit Postgres จะ replicate การเปลี่ยนแปลงนั้นออกไป และ subscription นี้ก็จะ fire พร้อม row ใหม่เป็น payload
supabase .channel('posts-feed') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' }, (payload) => { // payload.new is the row just inserted in step 2 addPostToFeed(payload.new); } ) .subscribe();สิ่งนี้ทำงานได้ก็เพราะ posts ถูกเพิ่มเข้าไปใน publication supabase_realtime แล้ว — ขั้นตอน setup จาก module Realtime ถ้าไม่มีขั้นตอนนี้ การเปลี่ยนแปลงของ table จะไม่ถูก replicate ออกมาเลย ไม่ว่า listener .on('postgres_changes', ...) ตัวนี้จะเขียนถูกต้องแค่ไหน insert ใน step 2 ก็จะยังสำเร็จอยู่ดี แต่ callback ตัวนี้จะไม่ fire เลย
สาม service หนึ่ง feature
หัวข้อที่มีชื่อว่า “สาม service หนึ่ง feature”ไม่มีอะไรพิเศษที่เป็น integration เฉพาะระหว่าง Storage, REST API และ Realtime — แต่ละ service ทำสิ่งที่ทำใน module ของตัวเองตามปกติ และ client code คือสิ่งที่ผูกทุกอย่างเข้าด้วยกันเป็น feature เดียวที่ user เห็น client ที่ upload ไม่เคยคุยกับ client ที่ subscribe อยู่โดยตรง flow นี้ทำงานได้เพราะทั้งคู่คุยกับ Supabase project เดียวกัน และ replication stream ของ Postgres คือสิ่งที่เชื่อม write ฝั่งหนึ่งเข้ากับ notification อีกฝั่ง
flowchart LR
upload["storage.from('photos').upload(file)"] --> insert["from('posts').insert({ photo_path, user_id })"]
insert -->|"allowed only if RLS policy passes"| commit[("Row committed in Postgres")]
commit -->|"replicated because posts is in supabase_realtime"| notify["postgres_changes INSERT event"]
notify --> other["Other client's .on('postgres_changes', ...) callback fires"]