ลองเล่น Supabase กับ Next.js กัน
/ 12 min read
สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video
Supabase คืออะไร ?
Ref: https://supabase.com/docs
Supabase is an open source Firebase alternative. Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings.
Supabase คือ open source platform ที่ทำการเตรียมเครื่องมือให้นักพัฒนาสามารถขึ้นงาน application ได้อย่างรวดเร็วขึ้น ซึ่งหลายๆคนจะมองว่า Supabase มีลักษณะใกล้เคียงกันกับ Firebase เลย (มี Service หลายๆตัวใกล้เคียงกันด้วย) โดย feature หลักๆของ Supabase คือ
- Database ใช้ PostgreSQL ซึ่งถือว่าเป็น DB สุดทรงพลังที่สามารถ query ข้อมูลได้หลากหลายประเภท รวมถึงสามารถทำ complex queries, relation (ตามคุณสมบัติของ SQL) ได้
- มี Authentication service ที่สามารถทำ Service Auth เอง หรือจะใช้ร่วมกับ 3rd party อย่าง Google, GitHub, Facebook, X (Twitter) ก็ได้
- มี Feature realtime ที่สามารถดึงข้อมูลแบบ realtime เมื่อมี change กับ data เกิดขึ้นได้
- มี Storage ที่สามารถเก็บไฟล์ขนาดใหญ่เช่น ภาพ, video หรือไฟล์ประเภทต่างๆที่อนุญาตให้ user upload เข้ามาได้
- มี API ที่ supabase ได้ทำการ generate ออกมาเพื่อให้สามารถต่อไปยัง database เพื่อให้สามารถจัดการ CRUD (Create, Read, Update, Delete) กับ Database ได้ง่ายขึ้น
- มี Edge Functions ที่ทำให้ supabase สามารถ run code แบบ server-side code ออกมาได้
- มี Dashboard สามารถจัดการกับทุก Service ของ supabase ได้ (รวมถึงสามารถใช้คำสั่ง query ใน Dashboard ได้)
จุดเด่นจริงๆเลยของ Supabase คือ “เชื่อมง่าย” และ “ตัวอย่างเยอะ” เนื่องจากเอกสารแทบจะเตรียมวิธีไว้ให้หมดแล้วว่าวิธีเชื่อมแต่ละอย่าง วิธีใช้งานแต่ละอันสามารถทำอย่างไรได้บ้าง เดี๋ยวเรามาเรียนรู้ไปพร้อมๆกันในบทความนี้
และอีกจุดหนึ่ง (ซึ่งเป็นจุดชูโรงที่ Supabase เทียบกับ Firebase ด้วย) คือการ “ไม่คิด pricing ตาม request”
Ref: https://supabase.com/pricing
ถ้าสังเกตตรงหน้า Pricing ของ supabase จะเห็นว่า supabase ใช้ประโยคนี้เป็นประโยคแรกในหัวข้อ Free “Unlimited API requests” นั่นคือ สามารถยิง Request เท่าไหร่ก็ได้เลยไม่จำกัด แต่จะไปจำกัดส่วนที่เป็น Brandwidth, Concurrent หรือ ขนาดข้อมูลแทน เลยเป็นอีกจุดพิจารณาสำหรับการเลือกใช้ระหว่าง Firebase และ Supabase เช่นเดียวกันว่า ชอบการคิด Pricing แบบไหนมากกว่ากัน
Next.js เราจะเลือกนาย
Ref: https://supabase.com/docs/guides/getting-started/quickstarts/nextjs
เพื่อให้ทุกคนรู้จักกับ Supabase มากขึ้น เราจะพามาเล่นผ่าน Framework Next.js
กัน เนื่องจากเป็น Framework ที่ผู้ติดตามของเรามีคนใช้มากพอสมควร รวมถึงตัวอย่างของ Next.js
ครอบคลุมทั้งฝั่งของ Client side (ที่เป็น React) และ ฝั่งของ Server side (ที่เป็น Node.js ที่ Next ห่อไป) จะได้เห็นภาพการใช้งานทั้ง 2 ฝั่งไปพร้อมๆกันได้
ตัวอย่างที่เราจะหยิบมาทำนั้น เป็นตัวอย่างที่ใช้สารตั้งต้นตาม quichstart next.js
ของ supabase เลย โดยจะทำการดัดแปลงบางส่วน และเพิ่ม query บางส่วน (ที่เกี่ยวข้องตามโจทย์เรา) เข้าไปแทน
ซึ่ง ตัวอย่างนอกเหนือจาก quickstart แล้วใน supabase มีการเตรียมอีกตัวอย่างหนึ่งไว้คือ https://supabase.com/docs/guides/auth/managing-user-data โดยมีตัวอย่างจัดการ Permission และมีตัวอย่างเรื่องของ Login ด้วย Magic Link อยู่ด้วย (เพื่อใครสนใจ usecase นี้ สามารถเข้าไปอ่านเพิ่มเติมได้)
ดังนั้นเพื่อให้เกิดความเข้าใจที่ถ่องแท้ ขอให้รู้จัก Next.js
มาก่อนอ่าน (หรือดู video) ของบทความนี้ด้วย หากใครยังไม่รู้จัก Next.js มาก่อน ขอแนะนำ อ่านที่นี่ เลย
เราจะทำอะไรกันบ้าง
เราจะทำระบบลงทะเบียนกัน โดยเราจะแยกออกเป็น 2 ฝั่งคือ
- ฝั่งหน้าบ้าน (สำหรับผู้ใช้ทั่วไป)
- สามารถกรอกฟอร์มเพื่อ submit ข้อมูลเข้ามาได้ โดยข้อมูลจะประกอบด้วย fullname (ชื่อ), email และ tel (เบอร์โทรศัพท์)
- สามารถแนบ upload ไฟล์มาได้ (จะนำไปเก็บไว้ใน supabase storage)
ให้อารมณ์เหมือนทำหน้าเว็บพร้อมกรอกใบสมัครพร้อมเอกสารเข้ามา
- ฝั่งหลังบ้าน (สำหรับ user ที่ login)
- แสดง user ทั้งหมดที่ทำการกรอกเข้าระบบมา สามารถดูไฟล์แนบเขาได้
- ทำ Search ได้ รวมถึงสามารถทำ pagination ได้
เราจะทำตัวอย่างประมาณนี้กัน let’s code ทุกคน
เริ่มต้น code กัน
เริ่มต้น project ตาม guideline ของ supabase เลย โดยการ run ผ่าน command
หลังจากนั้น หยิบ key จาก dashboard วางใน .env.local
ตามใน Dashboard ของ supabase
โดยมันจะอยู่ในส่วนของ Project Setting
> API
จะมีแสดงส่วน key ออกมาอยู่
ให้นำ key ตรงตำแหน่ง ANON มาใส่ NEXT_PUBLIC_SUPABASE_ANON_KEY
และ URL มาใส่ NEXT_PUBLIC_SUPABASE_URL
เสร็จแล้ว ลอง run project ดูด้วยคำสั่ง
ก็จะเจอว่าสามารถที่จะใช้งานได้ทันที
- สามารถใช้งานส่วนของ login / logout ออกมาได้เลย เราจะคง code ส่วนนี้ไว้ สามารถนำไปใช้ดูเป็นตัวอย่างได้ เนื่องจากเป็น pattern การทำ authentication email / password ของ supabase อยู่แล้ว
โดยนี่คือ structure ที่ CLI ของ next template supabase ได้เตรียมไว้ให้ โดย เราจะทำการลบให้เหลือแค่่ส่วนที่จำเป็นตาม structure ด้านล่างนี้
ส่วนที่สำคัญๆของ Structure นี้คือ ตัวที่อยู่ใน utils
เป็นตัวที่ supabase CLI เตรียมเอาไว้ให้ เพื่อให้เราสามารถ import คำสั่ง supabase ผ่าน folder utils
ได้ทันที (ตามชื่อ folder เลย)
** สามารถดูตัวอย่างได้จากการ import supabase ใน code หลังจากนี้ เดี๋ยวเราจะค่อยๆไล่ทำทีละ step กัน
ก่อนจะเริ่ม code ต้องสร้าง schema ก่อน
สิ่งที่เราจะต้องทำที่ supabase ก่อนคือ
- ต้องสร้าง table สำหรับเก็บข้อมูล user เอาไว้ก่อน (ซึ่งเราจะตั้งชื่อว่า
users
) - ต้องสร้าง storage สำหรับเก็บไฟล์ที่ user upload เข้ามา (ซึ่งเราจะตั้งชื่อว่า
attachments
)
โดยทั้ง 2 อย่างนี้สามารถสร้างได้ผ่าน dashboard supabase ได้เลย
สร้าง table
สามารถเพิ่ม table ได้จาก Table Editor
โดยสามารถทำได้ 2 วิธีคือ 1. ทำผ่าน UI (สามารถกดได้ผ่าน New Table
แล้วใส่ประเภทข้อมูลตามภาพนี้ได้เลย และตั้งชื่อว่า users
)
หรือ 2. ทำผ่าน query โดยนำ SQL นี้ไปใส่ตรง SQL Editor
ของ Dashboard supabase ก็จะสามารถสร้าง table ได้เหมือนกัน (ถ้าใครเคยใช้ MySQL เหมือนเวลาเราใช้ phpmyadmin เลย)
เมื่อสร้างเรียบร้อยกลับมาที่เมนู Table Editor จะต้องเจอ table users
สร้าง storage
สำหรับการสร้าง storage ให้ทำการกดสร้างจากเมนู Storage และกดสร้างเป็น Bucket ใหม่โดยตั้งชื่อว่า attachments
และเปิดเป็น Public bucket เพื่อให้สามารถเปิด url เป็น Public url ออกมาได้
เมื่อสร้างเรียบร้อยกลับมาที่เมนู Storage จะต้องเจอ attachment อยู่
กำหนด Policy
Ref: https://supabase.com/docs/guides/auth/row-level-security
Postgres Row Level Security (RLS) คือ feature ของ Postgres ที่อนุญาตให้เราสามารถควบคุมการจัดการคำสั่งไม่ว่าจะเป็น SELECT/INSERT/UPDATE/DELETE
statement ใน table ของ Postgres ได้ เช่น
- สามารถกำหนดได้ว่า user ที่ login เท่านั้นสามารถ UPDATE ได้
- user ที่ต้องอยู่ใน table author เท่านั้นถึงจะสามารถ SELECT ได้ เป็นต้น
ซึ่งด้วย feature ของ RLS นั้นจะ บังคับ ให้ table ใน Postgres ต้องมี Policy ก่อนถึงจะสามารถจัดการกับข้อมูลได้ (คือต้องอนุญาตถึงจะสามารถทำได้ แทนที่จะทำได้เลยเป็น default)
ซึ่งใน Supabase นั้น สามารถใช้ RLS ได้ผ่าน feature ที่ชื่อ Policies
(https://www.postgresql.org/docs/current/sql-createpolicy.html) โดย supabase นั้นมีหน้า UI ที่สามารถใส่ policy เข้าไปผ่าน Dashboard ได้ ไม่ว่าจะเป็นทั้ง table schema หรือ storage เราจะมาเริ่มใส่ Policy ตามโจทย์กัน
ใส่ Policy table users
โจทย์ของ Table users คือ
- มีแค่เฉพาะ คนที่ login เท่านั้น ถึงจะสามารถดูข้อมูล user ทุกคนได้
- บุคคลทั่วไป สามารถส่งข้อมูลเข้ามาเพิ่มได้ แต่ แก้ไข, ลบ ไม่ได้
โดยเราสามารถเพิ่ม Policy ได้จากการกด ลูกศรลง ตรง table และเลือกตรง View Policy
จะสามารถมายังหน้าของ Policy ได้
หลังจากนั้นกด New Policy
เพื่อเพิ่ม Policy เข้าไปใหม่
จากโจทย์เรานั้น Policy ที่เราจะเพิ่มเข้าไปจะมีด้วยกัน 2 Policy คือ
SELECT
สำหรับ user ที่authenticated
INSERT
สำหรับ user แบบ public ทั่วไป
และนี่คือ Policy ทั้งหมดของโจทย์นี้
ใส่ Policy storage attachments
โจทย์ของ Storage attachment คือ
- มีแค่เฉพาะ คนที่ login เท่านั้น ถึงจะสามารถดูข้อมูลไฟล์ที่ upload เข้ามาได้
- บุคคลทั่วไป สามารถส่งข้อมูลเข้ามาเพิ่มได้ แต่ แก้ไข, ลบ รวมถึงกลับมาดูไม่ได้
โดย Storage นั้นจะสามารถเพิิ่มได้จากหัวข้อ Storage > Policies
จากโจทย์เรานั้น Policy ที่เราจะเพิ่มเข้าไปจะมีด้วยกัน 2 Policy คือ
SELECT
สำหรับ user ที่authenticated
INSERT
สำหรับ user แบบ public ทั่วไป
และนี่คือ Policy ทั้งหมดของ Storage
เพียงเท่านี้ ทั้ง Table และ Storage เราก็จะพร้อมสำหรับการเริ่มงานเป็นที่เรียบร้อย step ต่อไปเราจะมาเริ่ม code กัน
1. ฝั่งหน้าบ้าน
ไฟล์ที่เกี่ยวข้อง
ตามที่เราอธิบายไว้ตอนแรก โจทย์หลักของฝั่งหน้าบ้านจะมี 2 โจทย์คือ
- สามารถกรอกฟอร์มเพื่อ submit ข้อมูลเข้ามาได้ โดยข้อมูลจะประกอบด้วย fullname (ชื่อ), email และ tel (เบอร์โทรศัพท์)
- สามารถแนบ upload ไฟล์มาได้ (จะนำไปเก็บไว้ใน supabase storage)
ผลลัพธ์จะได้ออกมาประมาณนี้
เราจะเริ่มแก้ไขที่ละไฟล์กัน
ที่ page.tsx
สิ่งที่เราจะเพิ่ม
- ทำการสร้าง Form สำหรับกรอก fullname, email, tel ขึ้นมา
- ปรับ component นี้เป็น
Client component
(เพื่อให้สามารถ handlr state ของ error message ด้วยคำสั่งของ React ได้) - ทำการ import
register()
ซึ่งจะเป็น Server action ที่ทำการต่อไปยัง server ตอน submit form - ทำการเชื่อม state ของการ submit form กับ Server action
register()
เพื่อให้สามารถแสดงผล state ผ่านหน้าจอออกมาได้ (แสดงข้อความสำเร็จ หรือมีปัญหาออกมาได้)
ที่ actions.ts
สิ่งที่เราจะเพิ่ม
- ทำการเพิ่ม function Server action
register()
เข้ามาสำหรับรับค่าจาก Form ของฝั่ง Client component - ทำการอ่านค่า fullname, email, tel, attachment ผ่าน
formData
เข้ามา - อ่านค่าจาก attachment และดำเนินการ upload เข้า storage ก่อน โดยจะทำการเปลี่ยนชื่อไฟล์ที่ generate ใหม่ด้วย
uuid()
เพื่อให้ชื่อไฟล์ไม่ซ้ำกัน ผ่านคำสั่งsupabase.storage.from('attachments').upload(uuidFileName, attachment)
- หลังจาก upload เสร็จให้เอา public path มา (เพื่อเก็บไว้ใน database) ผ่านคำสั่ง
supabase.storage.from('attachments').getPublicUrl(uuidFileName)
- นำข้อมูลทั้งหมดมาประกอบกัน โดยทำการ insert ข้อมูลเข้า table
users
ผ่านคำสั่งsupabase.from('users').insert()
- เมื่อเรียบร้อยให้ส่ง message กลับไปบอกผ่าน form ว่าบันทึกข้อมูลสำเร็จ (และส่ง message กลับไปบอกใน case ที่มี Error เกิดขึ้น)
2. ฝั่งหลังบ้าน
จากด้านบน โจทย์ของฝั่งหลังบ้านคือ
- แสดง user ทั้งหมดที่ทำการกรอกเข้าระบบมา สามารถดูไฟล์แนบเขาได้
- ทำ Search ได้ รวมถึงสามารถทำ pagination ได้
ไฟล์ที่เกี่ยวข้อง
ผลลัพธ์จะออกมาเป็นแบบนี้
ที่ login/page.tsx
อย่างแรก
- หน้า login ไม่ต้องทำอะไร สามารถใช้ตามตัวเดิมได้ แต่ปรับเรื่อง link และตัดส่วน Back ออกพอ
- code จะคล้ายๆกับที่ CLI generate มาให้แล้ว
ที่ user/page.tsx
สิ่งที่เราจะทำในหน้านี้คือ
- ทำการเพิ่มการเรียก
<AuthButton />
(ส่วนที่มีการเชื่อม login เอาไว้ที่ CLI ทำการ generate เอาไว้) เพื่อใช้สำหรับการ handle state login (ในกรณีที่ยังไม่ login จะได้สามารถกลับไปยังหน้า login ได้) - เรียกใช้
<UserManagement />
ซึ่งเป็นส่วนสำหรับการแสดงผลข้อมูล user ทั้งหมดของระบบออกมา แยกส่วนออกเป็น Client Component เนื่องจากเราจะมีการใช้งานร่วมกับ state ของ Client (ส่งผลทำให้ไม่สามารถใช้งานจากบน Server Component โดยตรงได้)
Step ต่อไปเราจะไป implement ตัว UserManagement.tsx
ที่เป็นตัวหลักกันต่อ
ที่ components/UserManagement.tsx
สิ่งที่เราจะทำ
- เพิ่ม query สำหรับการดึง user ออกมาด้วย
supabase.from('users').select('*')
- ทำการเพิ่ม searchbox และจัดการ handle search ผ่าน
.like('fullname', $searchValue)
- ทำ pagination โดยการ handle state page เพิ่มมา และทำการเรียกข้อมูลแบบ pagination ผ่าน
.range((page - 1) * itemsPerPage, page * itemsPerPage - 1)
ผลลัพธ์ทั้งหมดเมื่อมีการเพิ่มส่วนนี้เข้าไป
- จะสามารถค้นหาข้อมูล user ได้
- จะสามารถทำ pagination ได้ สามารถเปลี่ยนไปมาระหว่างหน้าได้
ที่ middleware.ts
สุดท้ายป้องกันคนที่ไม่ได้ login เข้ามาได้ โดยการเพิ่มส่วนของ middleware เข้ามาโดย
- ทำการเพิ่มการเรียกข้อมูล user เข้ามา recheck ก่อน แต่จะเรียกเช็คเฉพาะ path
/users
เท่านั้น (เพื่อจะได้ไม่ต้องเปลืองการดึงข้อมูล user เกินความจำเป็น) - หากไม่เจอข้อมูล user = ให้ redirect กลับไปยังหน้า
/login
เพื่อให้ทำการ login ก่อนเข้ามาหน้านี้
เทียบระหว่าง Supabase และ Firebase
และนี่ก็คือการใช้งาน Supabase กับ Table และ Storage โดยประมาณ ส่วนตัวของผม จุดที่ผมชอบจริงๆของ supabase เลยคือเรื่องของ “document ตาม usecase” ถือเป็น 1 ใน service ที่มีตัวอย่างเตรียมไว้ค่อนข้างมาก รวมถึงการเตรียมตัวอย่างคำสั่งที่ครอบคลุมด้วย ทำให้หยิบมาใช้งานได้ไม่ยากเช่นเดียวกัน
หากจะพิจารณาระหว่าง Firebase กับ Supabase สำหรับผม ผมจะพิจารณา 2 เรื่องใหญ่ๆคือ
- ต้องการ
SQL
หรือNoSQL
- ถ้ายังต้องการคุณสมบัติของ
SQL
อยู่ (ต้องการความเป็น relation อยู่) = Supabase ตอบโจทย์นี้แน่นอน - ถ้าอยากจัดการข้อมูลแบบ dynamic (ไม่ต้อง strict โครงสร้าง) = Firebase ก็จะตอบโจทย์เรื่องนี้กว่า
- Pricing
- ถ้าไม่ต้องการคำนวน Pricing ตามจำนวน Request = Supabase ตอบโจทย์กว่า
- ถ้าอยากได้ Pricing แบบคำนวนทุกจุดตามการใช้งานจริง (pay per use) = Firebase อาจจะตอบโจทย์กว่า
- Policy
- ถ้าต้องการความสามารถป้องกัน Security แบบ SQL (Row Level Security) = เลือก Supabase เลย
- ถ้ายังคงชอบการจัดการ Rule ผ่าน JSON อยู่ = เลือก Firebase เลย
ส่วนที่เหลือ อาจจะต้องพิจารณาเป็น service by service ไปว่า มีของตามที่เราต้องการครบไหม / มีจุดไหนขาดไปไหม ซึ่งส่วนตัวผมมองว่า จากที่ลองใช้มา Supabase จะค่อนข้างตอบโจทย์กับ Web application แบบทั่วไป (โดยเฉพาะ CMS) มากกว่า Firebase ถ้าเราใช้เพียง Database, Backend function และ เน้นเรื่องการดึงข้อมูล Request ถี่ๆ (ที่จะไม่ต้องกังวลเรื่อง Pricing จากการ call request)
แต่ในแง่จุดแข็งของ Firebase ก็ยังคงมีเรื่องการใช้งานร่วมกับ Google Service (เช่น Cloud run, Cloud function หรือ Cloud service อื่นๆของ Google) ดังนั้น ถ้า Service ของเรานั้นยังสะดวกต่อการใช้ Google Service มากกว่า Firebase ก็ยังคงตอบโจทย์อยู่เช่นเดียวกัน ส่งผลทำให้ทำโจทย์ได้กว้างกว่าเช่นเดียวกัน รวมถึงการจัดการแบบ realtime ที่เรียบง่ายกว่าและสามารถจัดการผ่าน JSON ได้ สำหรับ case realtime ก็ยังคง implement ง่ายกว่าฝั่ง Supabase เช่นเดียวกัน
ดังนั้นหากจะเลือกว่าใช้ Technology ตัวไหนในการทำ application ให้คำนึงตามโจทย์เป็นหลักว่า
- Firebase / Supabase ใครตอบโจทย์ได้ครบหมดหรือไม่ ?
- และ Pricing ระหว่างคิดตาม Request (Pay as you go แบบ Firebase) กับคิดแบบ Fixed (ใช้ตาม Quota จ่ายแบบเหมาแบบ Supabase) แบบไหนคำนวนได้ง่ายกว่าหรือถูกกว่า
ลองพิจารณากันตามความเหมาะสมนะครับ 😁
Github Source code
https://github.com/mikelopster/next-register-supabase
- มารู้จักกับ SQL Transaction กันว่ามันคืออะไร ?มี Video
มารู้จักเรื่องราวของการทำ Transaction และ Deadlock ผ่าน SQL กันว่ามันคืออะไร
- NestJS และ Mongoมี Video
เรียนรู้การผสานพลังระหว่าง NestJS framework ยอดนิยมฝั่ง Node.js กับ MongoDB ฐานข้อมูล NoSQL สุดทรงพลังกัน
- Astro และ Static site generatorมี Video
มารู้จักกับ Astro Framework สำหรับทำเว็บ Static สำหรับเว็บทำ Content โดยเฉพาะกัน
-