🗄️ บทที่ 6: Prisma & Database
เรียนรู้การใช้ Prisma ORM เวอร์ชันล่าสุด (6.8.0) กับ Next.js 15 สำหรับจัดการฐานข้อมูลอย่างมีประสิทธิภาพ
วัตถุประสงค์การเรียนรู้
เข้าใจ Prisma ORM 6.x และการติดตั้งใน Next.js 15
สามารถสร้าง Database Schema และ Migrations ได้
ใช้ Prisma Client กับ Server Components และ API Routes
เรียนรู้ Prisma Postgres และ TypedSQL (ฟีเจอร์ใหม่ล่าสุด)
🤔 Prisma คืออะไร?
Prisma เป็น Next-generation ORM (Object-Relational Mapping) ที่ช่วยให้การทำงานกับฐานข้อมูลใน Node.js/TypeScript ง่ายและปลอดภัยขึ้น พร้อมด้วย type safety และ developer experience ที่ยอดเยี่ยม
🌟 ฟีเจอร์เด่นของ Prisma 6.x:
Type Safety (Enhanced)
Generated TypeScript types ที่อัพเดตอัตโนมัติตาม database schema (Prisma 6.x)
Prisma Postgres (GA)
Managed PostgreSQL service ของ Prisma พร้อม zero cold starts
TypedSQL
Type-safe raw SQL queries ด้วย .sql files และ auto-completion
Omit API (GA)
ซ่อน fields ที่ไม่ต้องการในการ query (ผ่าน GA แล้ว)
💻 การติดตั้งและ Setup Prisma
ติดตั้ง Prisma
ติดตั้ง Prisma CLI และ Client สำหรับ Next.js 15
# ติดตั้ง Prisma CLI npm install prisma@latest --save-dev # ติดตั้ง Prisma Client npm install @prisma/client@latest # ติดตั้ง database driver (ตัวอย่าง PostgreSQL) npm install pg @types/pg # หรือใช้ Prisma Postgres (แนะนำ) # ไม่ต้องติดตั้ง driver เพิ่ม
Initialize Prisma
ตั้งค่า Environment Variables
สร้าง Prisma Client Instance
📝 การสร้าง Database Schema
มาเรียนรู้การเขียน Prisma Schema เพื่อกำหนดโครงสร้างฐานข้อมูล
📄 Basic Schema Structure (Prisma 6.x)
// prisma/schema.prisma generator client { provider = "prisma-client-js" // Prisma 6.x features previewFeatures = ["typedSql", "relationJoins"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(uuid(7)) // UUIDv7 support email String @unique name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations posts Post[] profile Profile? @@map("users") } model Post { id String @id @default(uuid(7)) title String content String? published Boolean @default(false) authorId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt author User @relation(fields: [authorId], references: [id]) @@map("posts") }
🔄 Database Migrations
Migrations คือการจัดการการเปลี่ยนแปลง database schema อย่างปลอดภัยและควบคุมได้
หลังจากสร้าง schema แล้ว ต้องสร้าง migration เพื่อสร้างตารางในฐานข้อมูล
$ npx prisma migrate dev --name init
$ npx prisma generate
# Development commands npx prisma migrate dev # สร้างและ apply migration npx prisma migrate reset # รีเซ็ต database และ apply migrations ใหม่ npx prisma db push # Push schema ไป database (no migration) # Production commands npx prisma migrate deploy # Apply pending migrations npx prisma migrate status # ตรวจสอบ migration status # Utility commands npx prisma generate # Generate Prisma Client npx prisma generate --sql # Generate TypedSQL queries npx prisma db seed # Run seed script npx prisma studio # เปิด Database GUI # Prisma Postgres commands npx prisma dev # Start local Prisma Postgres npx prisma studio --browser none # Studio without browser
📊 CRUD Operations กับ Prisma
เรียนรู้การใช้ Prisma Client เพื่อทำ CRUD operations ใน Next.js 15
ใช้ Prisma ใน Server Components สำหรับ data fetching ด้วย React 19 และ Prisma 6.x
// app/users/page.tsx (Server Component) import { prisma } from '@/lib/prisma'; export default async function UsersPage() { // Prisma 6.x: Enhanced query performance const users = await prisma.user.findMany({ relationLoadStrategy: 'join', // New in Prisma 6.x include: { posts: true, _count: { posts: true } }, orderBy: { createdAt: 'desc' } }); return ( <div> <h1>รายชื่อผู้ใช้ ({users.length} คน)</h1> {users.map(user => ( <div key={user.id} className="border p-4 mb-4"> <h3>{user.name || 'No Name'}</h3> <p>อีเมล: {user.email}</p> <p>จำนวนโพสต์: {user._count.posts}</p> <p>สมัครเมื่อ: {user.createdAt.toLocaleDateString('th-TH')}</p> </div> ))} </div> ); } // TypedSQL Example (NEW in Prisma 6.x) import { getUserWithPosts } from '@/generated/sql'; export async function getServerSideProps({ params }: { params: { id: string } }) { const userWithStats = await prisma.$queryRawTyped( getUserWithPosts(params.id) ); return { props: { user: userWithStats[0] } }; }
สร้าง API routes สำหรับ CRUD operations ด้วย Next.js 15 async APIs
// app/api/users/route.ts (Prisma 6.x) import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; // GET: ดึงข้อมูล users พร้อม omit sensitive fields export async function GET() { try { const users = await prisma.user.findMany({ omit: { // Omit API - GA in Prisma 6.x password: true, resetToken: true }, include: { _count: { select: { posts: true } } } }); return NextResponse.json(users); } catch (error) { console.error('Database error:', error); return NextResponse.json( { error: 'ไม่สามารถดึงข้อมูลได้' }, { status: 500 } ); } } // POST: สร้าง user ใหม่ พร้อม enhanced validation export async function POST(request: NextRequest) { try { const body = await request.json(); const { name, email } = body; // Enhanced validation if (!email || !name) { return NextResponse.json( { error: 'กรุณากรอก name และ email' }, { status: 400 } ); } // Prisma 6.x: createManyAndReturn for better performance const user = await prisma.user.create({ data: { id: crypto.randomUUID(), // or use uuid(7) in schema name, email, }, omit: { password: true // ซ่อน sensitive data } }); return NextResponse.json(user, { status: 201 }); } catch (error: any) { // Handle Prisma 6.x errors if (error.code === 'P2002') { return NextResponse.json( { error: 'อีเมลนี้ถูกใช้แล้ว' }, { status: 400 } ); } console.error('Database error:', error); return NextResponse.json( { error: 'ไม่สามารถสร้างผู้ใช้ได้' }, { status: 500 } ); } }
สร้าง API routes สำหรับจัดการ record เดี่ยวๆ ด้วย async params
// app/api/users/[id]/route.ts import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; // GET: ดึงข้อมูล user โดย ID export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; const userId = parseInt(id); if (isNaN(userId)) { return NextResponse.json( { error: 'ID ไม่ถูกต้อง' }, { status: 400 } ); } const user = await prisma.user.findUnique({ where: { id: userId }, include: { posts: { orderBy: { createdAt: 'desc' } } } }); if (!user) { return NextResponse.json( { error: 'ไม่พบผู้ใช้' }, { status: 404 } ); } return NextResponse.json(user); } catch (error) { console.error('Database error:', error); return NextResponse.json( { error: 'ไม่สามารถดึงข้อมูลได้' }, { status: 500 } ); } } // PUT: อัพเดท user export async function PUT( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; const userId = parseInt(id); const body = await request.json(); const user = await prisma.user.update({ where: { id: userId }, data: body }); return NextResponse.json(user); } catch (error: any) { if (error.code === 'P2025') { return NextResponse.json( { error: 'ไม่พบผู้ใช้' }, { status: 404 } ); } return NextResponse.json( { error: 'ไม่สามารถอัพเดทได้' }, { status: 500 } ); } } // DELETE: ลบ user export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; const userId = parseInt(id); await prisma.user.delete({ where: { id: userId } }); return NextResponse.json( { message: 'ลบผู้ใช้เรียบร้อย' }, { status: 200 } ); } catch (error: any) { if (error.code === 'P2025') { return NextResponse.json( { error: 'ไม่พบผู้ใช้' }, { status: 404 } ); } return NextResponse.json( { error: 'ไม่สามารถลบได้' }, { status: 500 } ); } }
💡 Best Practices สำหรับ Prisma + Next.js 15
ใช้ Prisma Client Singleton (Enhanced)
ป้องกัน connection limit ด้วย global instance pattern และ transaction options
Leverage Prisma Postgres
ใช้ Prisma Postgres สำหรับ zero cold starts และ global performance
ใช้ TypedSQL สำหรับ Complex Queries
Type-safe raw SQL queries ด้วย .sql files และ $queryRawTyped
ใช้ Omit API แทน Select
ซ่อน sensitive fields ด้วย omit option (GA ใน Prisma 6.x)
Optimize Queries ด้วย relationLoadStrategy
เลือก 'join' หรือ 'query' strategy สำหรับ relation loading
ใช้ UUIDv7 สำหรับ Primary Keys
Temporally sortable UUIDs ด้วย uuid(7) function
⚡ Performance และ Caching ใน Next.js 15
🔄 Prisma 6.x Caching & Performance
// Server Components - Enhanced with Prisma 6.x export default async function PostsPage() { // Prisma 6.x: relationLoadStrategy for better performance const posts = await prisma.post.findMany({ relationLoadStrategy: 'join', // Database-level joins include: { author: { omit: { email: true // Hide sensitive data } } } }); return <PostList posts={posts} />; } // TypedSQL for complex queries import { getPostsWithStats } from '@/generated/sql'; export async function getComplexData() { const result = await prisma.$queryRawTyped( getPostsWithStats({ limit: 10, offset: 0 }) ); return result; } // Prisma Postgres with global caching (built-in) const prisma = new PrismaClient({ // Prisma Postgres handles caching automatically log: ['query', 'info', 'warn', 'error'], });
🎯 ยินดีด้วย! คุณเรียนจบบทที่ 6 แล้ว
ตอนนี้คุณมีความรู้เกี่ยวกับ Prisma ORM 6.8 และการจัดการฐานข้อมูลแล้ว พร้อมสำหรับการเรียนรู้เรื่อง React Hooks ในบทถัดไป
📚 สิ่งที่คุณได้เรียนรู้ในบทนี้:
การติดตั้งและตั้งค่า Prisma 6.8
การออกแบบ Schema และ Migration
CRUD Operations และ Prisma Client
TypedSQL และ Omit API (ฟีเจอร์ใหม่)
Prisma Postgres และ Connection Management