รู้จักกับ Prisma ORM
/ 25 min read
สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video
Prisma คืออะไร
Prisma ORM (Object Relational Mapping) คือเครื่องมือจัดการฐานข้อมูล open source สำหรับ application TypeScript และ JavaScript โดย Prisma ORM จะมีชุดเครื่องมือในการสร้างและจัดการโครงสร้างของฐานข้อมูล ส่งคำสั่งค้นหา (queries) รวมถึงสามารถโยกย้ายฐานข้อมูล (migrations) ได้อย่างมีประสิทธิภาพและใช้งานง่าย
คุณสมบัติหลักของ Prisma ORM:
- Prisma Client ระบบที่สร้างคำสั่ง query อัตโนมัติพร้อมตรวจสอบความถูกต้องของชนิดข้อมูล (type-safe query builder) ช่วยให้นักพัฒนาสามารถทำงานกับฐานข้อมูลได้ง่ายขึ้น โดยระบบจะแปลง model ที่กำหนดไว้ใน Prisma Schema ให้เป็นการดำเนินการ CRUD ที่พร้อมใช้กับฐานข้อมูล โดย Prisma Client จะรับรู้ถึงโครงสร้างฐานข้อมูล รวมถึงมีชุดคำสั่งที่หลากหลาย ทำให้มั่นใจในความถูกต้องของข้อมูลและลดโอกาสเกิดข้อผิดพลาดขณะใช้งาน
- Prisma Migrate เครื่องมือจัดการการเปลี่ยนแปลงโครงสร้างฐานข้อมูล ช่วยให้สามารถกำหนดเวอร์ชันและติดตามการเปลี่ยนแปลงต่างๆได้อย่างเป็นระบบ นักพัฒนาสามารถกำหนดสิ่งที่ต้องการปรับปรุงในโครงสร้างฐานข้อมูลได้ด้วยรูปแบบที่เข้าใจง่าย จากนั้น Prisma Migrate จะสร้างไฟล์ migration ให้โดยอัตโนมัติเพื่อนำไป update กับฐานข้อมูลจริงได้
- Prisma Schema เป็นส่วนสำคัญของ Prisma โดย schema file นี้ช่วยให้นักพัฒนาสามารถกำหนด Model และ Relation ต่างๆ ภายใน application ได้ โดย Schema นี้เปรียบเสมือนแหล่งข้อมูลเดียว (single source of truth) ของโครงสร้างฐานข้อมูลที่เครื่องมือต่างๆ ใน Prisma จะนำไปใช้สร้าง code และไฟล์ migration ได้
- Prisma ใช้งานร่วมกับฐานข้อมูลได้หลากหลายชนิด ไม่ว่าจะเป็น PostgreSQL, MySQL, SQLite, SQL Server หรือแม้แต่ MongoDB
Prisma ได้รับความนิยมอย่างมากกับการพัฒนาคู่กับ Node.js และ TypeScript แต่อย่างไรก็ตามก็สามารถใช้กับ JavaScript ได้เช่นกัน จุดเด่นของ Prisma คือการที่มันช่วยลดงานจุกจิกที่เกี่ยวกับฐานข้อมูลและใช้ API ที่เข้าใจง่ายยิ่งขึ้นในการโต้ตอบกับฐานข้อมูลได้ดียิ่งขึ้นด้วยเช่นกัน
Setup Prisma
เพื่อให้เข้าใจ Prisma มากขึ้นในหัวข้อนี้เราจะพาเล่น Prisma ผ่าน Next.js กันเพื่อให้เห็นภาพการใช้งานทั้งจากฝั่ง Backend (ผ่าน Server Component ของ Next.js ที่มี Node.js เป็น back อยู่) และ Frontend (ผ่าน Client Component ของ React)
โดย project ที่เราจะทำคือ เราจะทำเว็บสำหรับจัดการ Blog Content โดยเบื้องต้น ต้องสามารถ
- สร้าง / แก้ไข / ลบ Post ได้
- สามารถดึง Post ทั้งหมดออกมาได้
เราจะแยกทำออกเป็นทั้งหมด 3 ส่วนคือ
- Setup Schema และ Prisma Client
- ส่วนของ API (Backend) จะทำการต่อเข้ากับ PostgreSQL ผ่าน Prisma เป็น CRUD API ของ post
- ส่วนของ React (Frontend) จะทำการดึงข้อมูลมาใช้เพื่อแสดงผลออกผ่านหน้าเว็บโดยดึงจาก API ที่ต่อเข้ากับ PostgreSQL
นี่คือตัวอย่างของผลลัพธ์แรกที่เราจะทำกันออกมา
เราจะมาไล่ทำทีละ step กัน
1. Setup Project และ Prisma Client
ขั้นแรกสุดเราต้อง setup project ขึ้นมาก่อน โดยเราจะ setup ของทั้งหมด 3 อย่างขึ้นมาคือ
- project Next.js สำหรับส่วนของ Application
- PostgreSQL ซึ่งในทีนี้เราจะสร้างผ่าน docker-compose.yml ขึ้นมา (พร้อมกับ pgadmin ในหน้าจัดการ)
- prisma client ที่จะทำการสร้างพร้อมกับ schema ของ database และ migration ขึ้นมา
โดย เริ่มต้น step แรกสุด ในทีนี้เราใช้ Next.js ในการทำ ดังนั้นเราจะทำการ init project next.js ขึ้นมาตาม document https://nextjs.org/docs/getting-started/installation โดยใช้คำสั่ง
โดยทำการเลือกใช้เป็น TypeScript และ Tailwind CSS เพื่อให้สามารถจัดการ style ได้ง่ายขึ้น ที่เหลือสามารถเลือกเป็นค่า default ได้เลย
หลังจาก project run ขึ้นมาได้เรียบร้อย เราจะทำการวางไว้ก่อน เป็นอันจบ step แรกของการ setup
step ที่ 2 ทำการลง postgreSQL ผ่าน docker-compose โดยใช้ docker-compose.yml
ที่มีหน้าตาแบบนี้ เป็นการ start postgreSQL และ pgAdmin (หน้าสำหรับจัดการ database ของ postgreSQL) ขึ้นมา
หลังจากนั้นให้ทำการ start docker ขึ้นมาด้วยคำสั่ง
และเมื่อลองตรวจสอบด้วย docker ps
ดู และเจอว่ามี service 2 ตัวขึ้นแบบตามภาพนี้เรียบร้อย เท่ากับว่า postgreSQL และ pgAdmin ได้ run ขึ้นมาแล้วเรียบร้อย
หลังจากนั้น ให้ลองทดสอบเปิด pgAdmin โดยเปิดผ่าน localhost:5050 ดูทำการต่อผ่าน config ของ docker-compose.yml
และหากสามารถเปิด database connection มาได้ถือว่า เรา setup ทุกอย่างมาถูกต้องแล้ว
ตอนนี้เราก็มีทั้ง project Next.js และ database (PostgreSQL) พร้อมแล้วเรียบร้อย มาเข้าสู่ step สุดท้ายคือการสร้าง Schema และการ migration database ขึ้นมากัน
ที่ project Next.js ให้ลง package ของ prisma
(สำหรับจัดการ prisma schema ผ่าน CLI) และ @prisma/client
สำหรับจัดการ prisma ผ่าน node.js (ใน Next.js)
หลังจากนั้น ให้ทำการพิมพ์คำสั่งนี้เพื่อทำการสร้าง file เริ่มต้นของ schema ขึ้นมา
เมื่อเข้าไปในไฟล์ก็จะเจอค่าเริ่มต้นตามนี้
โดยจะมีการแบ่งออกเป็นทั้งหมด 2 block ไว้คือ Generator Block (ตรง generator) และ Datasource Block (ตรง datasource)
- Generator Block จะกำหนดวิธีที่ Prisma จะสร้าง Client ขึ้นมา โดย Client นี้ถือเป็นช่องทางหลักที่คุณจะใช้ติดต่อสื่อสารกับฐานข้อมูลของคุณผ่าน Prisma (โดยในทีนี้เราระบุเป็น “prisma-client-js” หมายความว่า Client ที่ถูกสร้างขึ้นจะเป็นภาษา JavaScript ซึ่งเหมาะสำหรับการใช้งาน application Node.js นั่นเอง)
- Datasource Block เป็นส่วนที่ทำการตั้งค่าการเชื่อมต่อกับฐานข้อมูล โดยที่
db
คือชื่อที่เราตั้งให้กับ datasource และprovider
ระบุประเภทของฐานข้อมูลที่ใช้ ในที่นี้postgresql
เป็นการบอกว่าฐานข้อมูลที่เราเชื่อมต่อนั้นเป็น PostgreSQL และ url แทนค่าเป็น url ของฐานข้อมูลซึ่งสามารถกำหนดได้ผ่าน environment variable อย่าง.env
ได้เลยเช่นกัน (เคสใน config นี้ เราจะทำการดึง DATABASE_URL ผ่าน.env
ออกมาได้)
เพราะฉะนั้น เอาจริงทั้ง 2 block นี้ถือว่า init มาพร้อมสำหรับการใช้งานแล้วเรียบร้อย ต่อมา เราจะทำการเพิ่มอีก 1 block เพิ่มขึ้นมานั่นคือ Model Block หน้าตาแบบนี้เพิ่มต่อเข้าไป
model Post ที่ถูกกำหนดไว้ใน Prisma schema นี้เปรียบเสมือนโครงสร้างของตารางสำหรับเก็บข้อมูล Post ในฐานข้อมูล ซึ่งประกอบด้วย
- id เป็นเลขจำนวนเต็มที่เพิ่มขึ้นอัตโนมัติ ทำหน้าที่เป็นตัวระบุเฉพาะสำหรับแต่ละ Post
- title เป็นช่องสำหรับใส่ชื่อเรื่องของ Post และเป็นส่วนที่จำเป็นต้องมี
- content เป็นช่องที่ใส่เนื้อหาของ Post
- createdAt เป็นช่องเก็บวันที่และเวลาโดยสร้างขึ้นอัตโนมัติ ทุกครั้งที่มีการสร้าง Post ใหม่
ซึ่งสิ่งนี้สร้างตาม spec ของ Prisma Schema ตาม document นี้ https://www.prisma.io/docs/orm/prisma-schema/overview (สามารถอ่านเพิ่มเติมได้ที่ document นี้เช่นกัน) โดยสิ่งนี้จะกลายเป็นตัวแทนของฐานข้อมูล ที่จะใช้สำหรับเป็นต้นแบบสำหรับการสร้าง table ใน database ต่อไปด้วยเช่นกัน
เมื่อกำหนด schema เรียบร้อย ให้ทำการพิมพ์คำสั่งนี้เพิ่มเข้าไป เพื่อทำการเริ่มต้น prisma client ออกมา
โดยใน Prisma คำสั่ง prisma generate
จะทำการ update Prisma Client ให้สอดคล้องกับการเปลี่ยนแปลงในไฟล์ Prisma schema โดยหน้าที่หลักของคำสั่งนี้คือ ****อ่านไฟล์ schema.prisma
แล้วสร้าง client (Prisma Client) ขึ้นมาโดยอัตโนมัติ ซึ่งมีความสามารถในการตรวจสอบชนิดข้อมูล เพื่อใช้งานร่วมกับฐานข้อมูลของเรา (ที่ประกาศผ่าน model) ได้
โดยจุดประสงค์ใหญ่ๆของการใช้ Prisma Client (ที่ถือว่าเป็นส่วนหลักที่ application จะทำการเชื่อมต่อไปยังฐานข้อมูล) คือ เป็นชุดคำสั่ง API สำหรับใช้ทำงานกับฐานข้อมูล เช่น สร้าง, อ่าน, อัปเดต และลบข้อมูล ได้ผ่าน Prisma Client นี้เอง
เมื่อใช้คำสั่งเรียบร้อย มันก็จะระบุว่าได้ทำการ update Prisma Client ไปที่ node_modules แล้วเป็นที่เรียบร้อย หลังจากนี้ เราก็สามารถใช้งาน Model ผ่านคำสั่งบน @prisma/client
ได้แล้ว (โดยทุกครั้งที่มีการแก้ไข schema จะต้องทำเสมอ เพื่อให้มี code สำหรับใช้งานที่ฝั่ง Prisma Client ได้)
step สุดท้ายก่อนที่เราจะลองต่อฐานข้อมูล แน่นอน ตอนนี้เราประกาศ schema มาแล้ว และทำการ setup config เรียบร้อย แต่เรายังไม่ได้สร้างฐานข้อมูลตาม schema ที่มีการกำหนดผ่าน model Post เอาไว้ และแน่นอน Prisma เองก็ได้อำนวยความสะดวกเรื่องนี้ไว้แล้วเช่นกัน โดยสามารถใช้ได้ผ่านคำสั่ง
โดยคำสั่งนี้
- จะสร้างไฟล์ migration ใหม่ขึ้นมาภายใน folder
migrations
ของ Prisma โดยไฟล์เหล่านี้จะมีคำสั่ง SQL ที่จำเป็นต่อการปรับเปลี่ยนโครงสร้างฐานข้อมูลให้ตรงกับschema.prisma
ณ เวลานั้น - ส่วนคำสั่ง
--name <ชื่อ migration>
จะช่วยให้เราตั้งชื่อให้กับ migration นั้นๆได้ ซึ่งจะช่วยระบุจุดประสงค์ของ migration เมื่อถูกเก็บอยู่ใน version control system (เพื่อทำให้กลับมาตรวจสอบได้ง่ายว่า database มีการ update อะไรไปบ้างในแต่ละรอบที่เกิด imgration) - หลังจากสร้างไฟล์
migration
แล้ว คำสั่งนี้จะปรับใช้migration
ไปยังฐานข้อมูลโดยอัตโนมัติ ซึ่งจะทำการ updatee โครงสร้างฐานข้อมูลให้ตรงกับที่ระบุไว้ในไฟล์schema.prisma
เช่น การเพิ่มตารางใหม่, ปรับเปลี่ยนตารางเดิม หรือลบตาราง (migration
จะวิเคราห์ออกมาผ่าน SQL ใน migration ให้เรียบร้อยแบบอัตโนมัติ) - หลังจากปรับใช้ migration ไปยังฐานข้อมูลเรียบร้อยแล้ว คำสั่งนี้จะ update Prisma Client ใหม่ เพื่อรับรองว่า Prisma Client จะทำงานร่วมกับโครงสร้างใหม่ของฐานข้อมูลได้อย่างสมบูรณ์
เราจะลอง run โดยตั้งชื่อ migration ว่า npx prisma migrate dev --name init
เมื่อทำการ run migration เรียบร้อย ให้ไปดูผลลัพธ์ที่ database ของ PostgreSQL ผ่าน pgAdmin ดู
ก็จะเจอตารางชื่อเดียวกันกับ model (Post) ออกมาได้ พร้อมกับตาราง _prisma*_*migrations
ที่เป็นการเก็บ version ของ database ที่มีการ migrate เข้าไป เพื่อทำให้เวลาที่ Prisma run migration ใหม่อีกรอบ จะได้ทราบได้ว่า มีการ run migration นี้ไปแล้วหรือไม่ออกมาได้ (เพื่อป้องกันการ run migration ซ้ำออกมาได้)
และนี่ก็คือ step การ setup โดยประมาณของการใช้ Prisma ไอเดียหลักๆคือ
- setup project พร้อมลง Prisma และ Prisma Client
- setup database ที่จะใช้
- สร้าง schema Prisma ขึ้นมา
- run migration เพื่อทำการสร้าง table ใน database และ Prisma Client API สำหรับให้ฝั่ง Client เข้ามาเชื่อมต่อกับ database ได้
Step ต่อไปเราจะเริ่มทดลองต่อเข้ากับ database กัน
2. ส่วนต่อ Backend
Step แรกสุดเราจะลองสร้าง API ทั้งหมด 4 ตัวออกมากัน คือ CRUD ของ posts นั่นเองประกอบด้วย
- GET
/api/posts
สำหรับดึง Post ทั้งหมดออก - GET
/api/posts/:id
สำหรับดึง Post ออกมาตาม id - POST
/api/posts
สำหรับสร้าง Post - PUT
/api/posts/:id
สำหรับแก้ไข Post ตาม id - DELETE
/api/posts/:id
สำหรับลบ Post ตาม id
เนื่องจากเราใช้ Next.js ดังนั้น เราจะใช้หลักการของ Route Handler ในการสร้าง API ออกมา https://nextjs.org/docs/app/building-your-application/routing/route-handlers ดังนั้น เราจะสร้าง structure project ตามนี้ขึ้นมาเพื่อทำ API ตามโจทย์นี้
โดย
api/posts/route.ts
จะทำการเก็บ API สำหรับการ Get Post ทั้งหมด และการสร้าง Post เอาไว้ โดย path ก็จะอ้างอิงตาม folder นั่นคือ/api/posts
api/posts/[id]/route.ts
จะทำการเก็บ API สำหรับการ Get Post ราย id, แก้ไข Post และ ลบ Post ตาม id ออกมา โดย path ก็จะอ้างอิงตาม folder นั่นคือ/api/posts/:id
ดังนั้นเริ่มต้นที่ api/posts/route.ts
ก่อน ทำการสร้าง API 2 path ขึ้นมา
จาก code
- function
GET
ที่มีหน้าที่ในการดึงข้อมูลทั้งหมดจากตารางpost
ในฐานข้อมูล และส่งกลับข้อมูลออกมาในรูปแบบ JSON โดยจะใช้ method ชื่อว่าfindMany
ของprisma.post
ซึ่งเป็น method ที่สร้างขึ้นมาโดยอัตโนมัติโดยอิงจาก modelPost
ที่เราได้กำหนดไว้ใน Prisma schema โดย function นี้จะถูกใช้เมื่อมีHTTP request แบบ GET เข้ามา - function
POST
ออกแบบมาเพื่อรับ HTTP request แบบ POST โดยเริ่มต้นด้วยการดึงข้อมูล title และ content ออกมาจากส่วน body ของ request ที่ส่งมาในรูปแบบ JSON จากนั้นจะใช้ method ชื่อcreate
ของprisma.post
เพื่อเพิ่มข้อมูลใหม่เข้าไปในตารางpost
ด้วย title และ content ที่ได้รับมา
ผลลัพธ์ของ code ก็จะเป็นตามนี้ ก็จะสามารถ ยิง GET / POST ตาม path ที่กำหนดออกมาได้
ต่อมาที่ api/posts/[id]/route.ts
ก่อน ทำการสร้าง API 3 path ขึ้นมา
จาก code
- function
GET
ทำหน้าที่ในการดึงข้อมูล post เดี่ยวๆ โดยระบุ id ของ post นั้น ซึ่งจะดึงค่า id มาจาก parameter ใน request จากนั้นแปลงเป็นตัวเลข และใช้prisma.post.findUnique
ไปค้นหา post ถ้าหาเจอก็จะส่งข้อมูลของ post นั้นกลับมาในรูปแบบ JSON - function
PUT
ทำหน้าที่อัปเดต title และ content ของ post อ้างอิงจาก id โดยจะดึง title และ content ใหม่ออกมาจากส่วน body ของ request ที่ส่งมาในรูปแบบ JSON แล้วเรียกใช้prisma.post.update
พร้อมกับระบุ id และข้อมูลใหม่เข้าไปด้วย ซึ่งถ้าอัปเดตสำเร็จ Prisma จะส่งข้อมูล post ที่อัปเดตแล้วกลับมาในรูปแบบ JSON - function
DELETE
ทำการลบ post ออกจากฐานข้อมูลโดยใช้อ้างอิงจาก id ของ post นั้น ระบบจะเรียกใช้prisma.post.delete
พร้อมกับระบุ id ของ post ที่ต้องการลบ
ผลลัพธ์ของ code ก็จะเป็นตามนี้ ก็จะสามารถ ยิง GET / PUT / DELETE ตาม path ที่กำหนดออกมาได้
และนี่ก็คือพื้นฐานการทำ CRUD API ผ่าน Next.js ผ่านคำสั่งของ Prisma ออกมา step ต่อมาเราจะเริ่มนำ API ไปต่อฝั่ง Frontend เพื่อเรียกใช้จริงตามตัวอย่างกัน
3. ส่วนต่อ Frontend
สำหรับ Frontend นั้น เพื่อให้เห็นภาพการใช้งานแบบแยกเคสกัน เราจะขอแยกทั้ง 3 หน้าออกจากกันนั่นคือ
-
หน้าสำหรับแสดง Post ทั้งหมดออกมา (และสามารถนำไปสู่หน้า สร้าง / แก้ไข และ ลบ Post ได้)
-
หน้าสำหรับการสร้าง Post
-
หน้าสำหรับการแก้ไข Post
ดังนั้นสำหรับส่วนของหน้า Frontend เราจะสร้างตาม structure นี้ โดยทุกหน้าที่เราสร้างขึ้นมาจะทำเป็น Client component เพื่อให้สามารถจัดการ state จากหน้าเว็บออกมาได้
โดย
app/page.tsx
จะเป็นหน้าหลักของเว็บ โดยจะแสดงเป็นหน้าแสดง Post ทั้งหมดออกมาapp/create/page.tsx
จะเป็นหน้าสำหรับการสร้าง Postapp/edit/[id]/page.tsx
จะเป็นหน้าสำหรับแก้ไข Post (โดยหยิบ id จาก param มาใช้)
โดยเพื่อให้ง่ายต่อการใช้งาน api เราจะขอลง library axios
เพิ่มเพือใช้สำหรับเรียกใช้งาน API
เริ่มต้นจาก app/page.tsx
สังเกตว่า
- หลักการไม่ต่างกับการเรียกใช้งาน API ปกติเลย โดยเป็นการเรียกใช้ API
GET /api/posts
เพื่อทำการดึง posts ทั้งหมดมาแสดงที่หน้าเว็บ - และเรียกใช้
DELETE /api/posts/:id
เมื่อมีการกดลบ Post จาก list ที่แสดงออกมา (โดยอ้างอิงตาม id ที่ส่งเข้ามา)
และก็เช่นเดียวกันกับหน้าสร้าง Post app/create/page.tsx
และหน้าแก้ไข app/edit/[id]/page.tsx
สังเกตว่า ทั้ง 3 หน้านั้นหลักการใช้งานก็คือการเรียกใช้งานผ่าน API ทั่วๆไปเหมือนกับการ call API ตามปกติ และถ้าทำทุกอย่างมาถูกต้องผลลัพธ์ก็จะเหมือนกับภาพแรกสุดที่เราเกริ่นไว้ตอนต้นออกมาได้นั่นเอง
Query กับ Prisma
เรามาเพิ่มเติม feature ของ Prisma กัน โดยนอกเหนือจากการดึงข้อมูล แก้ไขข้อมูลและลบข้อมูลตามปกติแล้ว Prisma เองก็มี feature Query สำหรับการทำ search, filter, sort เหมือนกับ ORM ทั่วไปเช่นกัน
ในตัวอย่างนี้เราจะโชว์ตัวอย่างเพิ่มเติมที่ทำให้หน้าเว็บสามารถ ค้นหา Post / filter Post (ตาม category) และ sort Post ตามเวลาที่สร้างออกมาได้
ดังนั้น สิ่งที่เราจะทำเพิ่มคือ
- เราจะเพิ่ม
category
ใน model ของ Post เข้ามา (เพื่อใช้งานร่วมกับ Filter) - update CRUD API ของ post ให้ ใช้งานร่วมกับ
category
รวมถึง UI ให้ใช้งานร่วมกับcategory
ออกมา (โดย UI จะทำการ fix UI)
เราจะลองมาเพิ่มไปทีละจุดกัน
ปรับ Schema Post
ที่ไฟล์ prisma/schema.prisma
ให้ทำการเพิ่ม category เข้ามาใน Post
หลังจากที่เพิ่ม field เข้ามาให้ทำการ run migrate เพิ่มอีก 1 version ขึ้นมา
เมื่อ run เรียบร้อย ให้ลองมาดู database ที่ pgadmin ก็จะเจอ column ใหม่ category
ขึ้นมาได้
และอย่างที่อธิบายไปตอนแรก คำสั่งของ Prisma Client เองก็จะทำการสร้าง field นี้มาให้เองอัตโนมัติเช่นเดียวกัน ดังนั้นเราจึงสามารถใช้งาน field category
ออกมาได้แล้วเช่นเดียวกัน
เพิ่มส่วน API (Backend)
สำหรับ API 2 files app/api/post/[id]/route.ts
และ app/api/post/route.ts
เองก็ต้องทำการเพิ่ม field category ทั้งการส่งออกและการนำเข้ามา
โดยเริ่มจาก app/api/post/[id]/route.ts
สังเกตจาก code
- ฝั่ง
GET
code เหมือนเดิม เนื่องจาก Prisma Client ทำการ handle field category เพิ่มมาแล้วเป็นที่เรียบร้อย - ฝั่ง
DELETE
ก็ไม่ต้องแก้อะไรเช่นกัน เนื่องจากไม่ได้ทำอะไรกับ field category เลย - สำหรับ
PUT
นั้น เพิ่มเพียง field category เข้ามาเท่านั้น เพื่อให้ support กับ category ที่ส่งแก้ไขมาเพิ่มได้
มาที่ app/api/post/[id]/route.ts
จาก code
- ฝั่ง
POST
ไม่ได้มีการปรับอะไรมากนอกจากเพิ่มcategory
เข้ามา และนำcategory
ไปสร้าง Post - ฝั่ง
GET
เพิ่มเติมตัวกรองข้อมูลเสริมเข้าไป ไม่ว่าจะเป็น category, search query และลำดับการแสดงผล (sort order) โดย function นี้จะอ่าน parameter ที่มาจากใน URL ของ request เพื่อเอามาใช้เป็นตัวกรอง ดังนี้- category กรอง Post ตามหมวดหมู่
- search ค้นหาชื่อหัวข้อของ Post โดยจะค้นหาแบบ insensitive case (ไม่สนใจตัวเล็ก-ใหญ่)
- sort กำหนดลำดับการแสดงผลตามข้อมูล timestamp ใน createdAt โดยค่าตั้งต้นจะเป็นเรียงแบบใหม่สุดไปเก่าสุด (desc)
- โดยตัว function จะสร้าง object
whereCondition
ตามค่าcategory
ที่ระบุมา จากนั้น function ก็จะไปค้นหา Post ในฐานข้อมูลด้วยคำสั่งprisma.post.findMany
โดยอาศัย where condition และข้อมูลในการเรียงลำดับ (orderBy) ที่สร้างขึ้นมา
และด้วยการเพิ่มสิ่งนี้เข้าไป ก็ทำให้ API Get /api/posts
ก็รองรับการ search, sort และ filter (จาก category) ออกมาได้นั่นเอง step ต่อไปเราจะนำ API เหล่านี้ไปใช้กับฝั่ง UI กัน
เพิ่มส่วน Frontend
เนื่องจากมีการเพิ่ม category เข้ามา ดังนั้นทั้ง 3 หน้าเองก็ต้องเพิ่มการใช้งาน category เข้ามา โดยไอเดียคือ
- ทั้ง 3 หน้าจะเพิ่ม dropdown category แบบ fixed (แบบ hard code ใส่เอาไว้)
- ใน หน้าแสดง Post ทั้งหมด
- ใน หน้าสร้าง / แก้ไข Post ให้ทำการดึง category และ map กับ dropdown เพื่อให้สามารถ สร้าง / แก้ไข category Post ได้
เราจะเริ่มจากหน้าสร้างและแก้ไขกันก่อน โดยทั้ง 2 files เราจะแก้ไขตามนี้
เริ่มที่ app/create/page.tsx
และที่ app/edit/[id]/page.tsx
โดยทั้ง Create และ Edit นั้นทำการเพิ่ม Dropdown ของ Category เข้ามาและทำการเพิ่ม category
field เข้าไปในเส้นของการ POST / PUT /api/posts
เหมือนกัน
ต่อมาที่หน้าแสดง Post ทั้งหมด เราจะทำการเพิ่ม search, filter และ sort เข้าไปในหน้า Post
จาก code ได้มีการเพิ่มเติม 2 ส่วนเข้ามา
- เพิ่มส่วน useEffect Hook โดยทันทีที่ component ถูกแสดงผล component จะดึงข้อมูล posts โดยเรียกใช้ function
fetchPosts
ซึ่ง function นี้จะสร้าง query string โดยใช้ค่าปัจจุบันของตัวแปรcategory
,search
และsort
แล้วส่ง GET request ไปยัง/api/posts
พร้อมค่า parameter เหล่านี้เข้าไปด้วย จากนั้นข้อมูล posts ที่ได้รับ ก็จะถูกเก็บไว้ใน state ที่ชื่อว่าposts
ออกมาได้ - โดยส่วนของการ search และ sort นั้น ที่ component นี้จะมีช่องกรอกข้อมูลและตัวเลือก เพื่อให้ผู้ใช้สามารถใส่คำค้นหา เลือกหมวดหมู่ และเลือกวิธีการเรียงลำดับได้ โดย function
handleApplyFilters
ซึ่งจะทำงานเมื่อผู้ใช้กดปุ่ม “Apply” จะทำการดึงข้อมูล posts มาใหม่อีกครั้งตามค่า filters และ sort ที่ระบุไว้
และเมื่อลอง run หน้าเว็บออกมา ผลลัพธ์ก็จะออกมาเหมือนกันกับตอนแรกออกมาได้
ทำ Relation Query
นอกเหนือจาก query ทั่วไปแล้ว การเล่นกับ relational database เองยังสามารถเล่นกับ relation ระหว่าง table ออกมาได้เช่นเดียวกัน โดยตัวอย่างนี้ เราจะลองเพิ่มเติมโดยการเปลี่ยน category จากแต่เดิมเป็นการ fix ค่าเอาไว้ ให้ดึงผ่าน database มาแทน โดย
- เราจะทำการปรับ category จากแต่เดิมที่เป็น string ใน Post ปรับเป็น categoryId ที่อ้างอิงจากตารางใหม่ที่จะสร้างขึ้นมาแทน
- ตารางใหม่ที่สร้างขึ้นมาคือตาราง category
ปรับ schema ใน prisma/schema.prisma
เป็น
ทีนี้เพื่อไม่ให้มี field category
ซ้ำซ้อน เราจะทำการเคลียร์ field category
และสร้าง field ใหม่เป็น categoryId
แทน (เป็นการเปลี่ยน category ให้เก็บเป็น relation แทน) ส่งผลให้เมื่อ run migrate ด้วยคำสั่งเดิม
ก็จะเจอคำถามว่า ขอเคลียร์ data แทนเนื่องจาก schema ของ data เปลี่ยนไปแล้วเรียบร้อย
เมื่อตอบ yes ไป ก็จะทำการ run migrate ใหม่โดยการเคลียร์ data ใหม่ทั้งหมดแทน เพียงเท่านี้ก็จะเป็นการเคลียร์ข้อมูลเก่าและ
กรณี นี้จะเกิดขึ้นเมื่อ table ที่มี column เดิมที่เคยสร้างอยู่มีการเปลี่ยนแปลงบางอย่างเกิดขึ้น (จริงๆมีหลายเคสาก) ดังนั้น หากมีการแก้ไข column เดิมใน table เดิม ขอให้ศึกษาเพิ่มเติมก่อนที่จะตัดสินใจลบ data ออกนะครับ
และหากทุกอย่างทำอย่างถูกต้อง ผลลัพธ์ก็จะออกมาเป็นตามนี้ได้ มี table เพิ่มมาเป็น 2 ตารางและ column ก็ของ Post ก็จะโดนแก้ไขไปเป็นแบบนี้แทน
เพิ่ม Category CRUD API
เราจะมาเพิ่ม API ส่วน Category กัน โดยรอบนี้ เราจะให้จัดการ Category ได้ผ่าน API เท่านั้น เราเลยจะเพิ่ม API มาแค่ 4 เส้นคือ
- GET
/api/categories
get category ทั้งหมด - POST
/api/categories
สร้าง category - PUT
/api/categories/:id
แก้ไข category - DELETE
/api/categories/:id
ลบ category
ดังนั้น เคสก็จะคล้ายๆเดิม สร้าง 2 file ที่ categories
เพิ่มเข้ามาสำหรับทำ API 2 เส้นเพิ่มเติม
ที่ api/categories/route.ts
ทำการเพิ่ม API สำหรับ GET ทั้งหมด และ สร้าง category ออกมา
ที่ api/categories/[id]/route.ts
ทำการเพิ่ม API สำหรับแก้ไขและลบ Category (จะเพิ่มเคส get รายตัวเหมือน Post ก็ได้ แต่เนื่องจาก Category เป็นข้อมูลที่มีไม่เยอะมาก และไม่ได้มีเคสใช้งานจริงที่ฝั่ง Frontend ดังนั้นเราจึงไม่ได้มีการเพิ่มเข้าไป)
หลังจากนั้นให้ลองยิง API ทั้ง 4 เส้นดู ถ้าสามารถสร้างได้และ get category ออกมาได้ (เหมือนกับ post) = การเพิ่มของเราเสร็จสิ้นเรียบร้อย
เพิ่ม Relation ใน CRUD Post API
เราจะกลับมาที่ API เส้นเดิมกันบ้างของ Post เนื่องจาก category
ตอนนี้โดนเปลี่ยน field ไปเป็นเก็บ relation เป็นที่เรียบร้อย ดังนั้นใน code ของ POST API เองก็ต้องแก้ไขเช่นเดียวกัน
ที่ 2 files ของ API จะต้องแก้ไขเป็นตามนี้เพื่อให้สามารถดึง category จาก table ข้างเคียงออกมาได้
ที่ api/posts/[id]/route.ts
โดยใน code นี้มีเปลี่ยนแค่การใช้ field จาก category
มาเป็น categoryId
แทน
ที่ api/posts/route.ts
จาก code
- ฝั่ง
GET
เพิ่มเติมจาก condition เดิมโดยการเปลี่ยนจากแต่เดิมหาcategory
ใน Post เอง ให้ไปหาใน table category แทนผ่านis
ใน where condition โดยคุยผ่าน relation category ไป - และตัวดึงข้อมูลผ่าน
prisma.post.findMany
ให้เพิ่มinclude: { category: true }
เข้าไปเพื่อให้สามารถดึงข้อมูล category มาใช้ร่วมกันกับ Post ได้ เพื่อให้สามารถดึงข้อมูล category จากอีกตารางมาแสดงที่ข้อมูล JSON เดียวกันได้ - ส่วนฝั่ง
POST
ก็แค่เพิ่มcategoryId
เข้าไปเพื่อให้สามารถ save category เข้ามาได้
เพื่อทดลองให้ถูกต้อง ให้ลองสร้าง post โดยใช้ categoryId และ get post ออกมา หากสามารถดึงข้อมูล category ออกมาได้ก็ถือว่าเป็นอันถูกต้องแล้ว
ปรับใช้กับฝั่ง Frontend
สุดท้ายที่ฝั่ง Frontend แม้จะหน้าตา UI ออกมาเหมือนเดิมก็ตาม แต่ category ได้มีการเปลี่ยนจาก hard code มาเป็นดึงจาก API แทน ดังนั้นโจทย์ของฝั่ง Frontend คือ
- เปลี่ยนมาใช้ category ผ่าน field ใหม่ที่ mapping ออกมา
- ตรง dropdown เปลี่ยนไปใช้ category จาก API
- ฝั่ง สร้าง / แก้ไข เปลี่ยนเป็น categoryId สำหรับการส่ง save แทน
ดังนั้น code 3 ส่วนก็จะประมาณนี้เริ่มจากส่วนของ สร้างและแก้ไข ก่อน
ที่ app/create/page.tsx
ที่ app/edit/[id]/page.tsx
และสุดท้ายที่หน้าแสดง Post ทั้งหมด app/page.tsx
และนี่คือผลลัพธ์ทั้งหมดของเรื่องราวนี้
สังเกตว่า เราก็จะสามารถหยิบ Category จาก API มาได้และผลลัพธ์ก็จะออกมาเหมือนเดิมกับตอนที่เรา set category ผ่าน Post ไว้ได้
และนี่คือตัวอย่างของการใช้ relation ระหว่างกัน จะสังเกตว่า เราปรับ code จากเดิม ไม่เยอะเลย และเรายังคงสามารถใช้คำสั่งเดิมในการดึงข้อมูลออกมาได้เช่นกัน
เพิ่มเติม API ดึงผ่าน Relation ของ Category
รวมถึงการ relation สามารถทำได้ทั้ง 2 ฝั่งเช่นกัน ในเคสด้านบนคือ เราสามารถดึง category ที่ติดอยู่กับ Post ออกมาได้ ทีนี้ หากเราทำกลับกันคือ ดึง Post ที่อยู่ใน category ออกมาแทน ก็สามารถทำได้เช่นเดียวกัน
เช่น สมมุติเราลองเพิ่ม API GET /api/categories/[id]/posts
โดยทำการ list post จากภายใน category นั้นออกมา ก็สามารถทำได้ผ่านคำสั่ง includes
ตัวเดิมเช่นกัน เช่นแบบนี้
เมื่อลองดูผลลัพธ์ผ่าน Postman ก็จะได้ post ทั้งหมดของ category นั้นออกมาได้
และนี่ก็คือตัวอย่าง Prisma ทั้งหมดภายในบทความนี้
เพิ่มเติมและสรุป
อย่างที่เราเห็น Prisma คือเครื่องมือ ORM (Object Relational Mapping) ที่เปี่ยมไปด้วยความสามารถมากมาย ถูกออกแบบมาให้ทำงานได้อย่างราบรื่นกับระบบฐานข้อมูลหลายประเภท นอกเหนือจากที่ Prisma จะรองรับ PostgreSQL ในปัจจุบัน Prisma ก็ได้ขยายขอบเขตความสามารถในการทำงานร่วมกับฐานข้อมูลหลักๆ แบบอื่นๆ ด้วย เช่น MySQL, SQLite, SQL Server รวมถึง MongoDB (ที่เป็น NoSQL) เพื่อตอบสนองความต้องการที่หลากหลายในการจัดการฐานข้อมูล
การที่สามารถทำงานร่วมกับฐานข้อมูลได้หลากหลาย ทำให้นักพัฒนาสามารถใช้ประโยชน์จากความสามารถต่างๆ ของ Prisma ได้เต็มที่ ตั้งแต่การสร้าง model ข้อมูลที่เข้าใจง่าย ระบบจัดการ migration และ client ที่มีระบบตรวจสอบชนิดข้อมูลเพื่อใช้ในการ query ข้อมูลต่างๆ โดยไม่ต้องกังวลว่าเราจะใช้เทคโนโลยีฐานข้อมูลอะไรอยู่เบื้องหลัง Prisma ช่วยลดความซับซ้อนในขั้นตอนการพัฒนาโปรแกรมลงได้ และเพิ่มความยืดหยุ่นรองรับความเปลี่ยนแปลงของฐานข้อมูลที่จะเกิดขึ้นในอนาคตได้อีกด้วย เช่นกัน
สำหรับใครที่สนใจเรื่องการนำ deploy จริงขอแนะนำบทความของ vercel เพิ่มเติม โดยจะแนะนำการใช้งานร่วมกับ Vercel Postgres ไว้ในบทความนี้ด้วย https://vercel.com/guides/nextjs-prisma-postgres
รวมถึง การต่อกับ MongoDB สามารถอ่านเพิ่มเติมผ่านบทความนี้เช่นเดียวกัน
หวังว่าทุกคนจะมีโอกาสได้ประยุกต์ใช้ Prisma กับ Project ตัวเองกันนะครับ
- รู้จักกับ Design Pattern - Creational (Part 1/3)มี Video
มาเรียนรู้รูปแบบการพัฒนา Software Design Pattern ประเภทแรก Creational กัน
- รู้จักกับ Storybook และการทำ Component Specsมี Video
มาลองทำ Component Specs และ Interactive Test ผ่าน Storybook กัน
- ลอง Rust Basic (1)มี Video
ลองไมค์สัปดาห์นี้ เรามาทำความรู้จักกับภาษา Rust กันน (เปิด line ผลิตใหม่ของช่อง mikelopster 😆)
- OAuth คืออะไร ?มี Video
มารู้จักกับพื้นฐาน OAuth กันว่ามันคืออะไร และสิ่งที่เรากำลังทำกันอยู่คือ OAuth หรือไม่