🗄️ บทที่ 6: Prisma & Database

เรียนรู้การใช้ Prisma ORM เวอร์ชันล่าสุด (6.8.0) กับ Next.js 15 สำหรับจัดการฐานข้อมูลอย่างมีประสิทธิภาพ
50 นาที
ขั้นสูง
สำคัญมาก
Prisma 6.8.0
Prisma Postgres
วัตถุประสงค์การเรียนรู้

เข้าใจ 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