Prisma เป็น ORM ยุคใหม่ที่ออกแบบมาสำหรับ TypeScript และ Node.js โดยเฉพาะ แตกต่างจาก ORM แบบดั้งเดิมตรงที่ Prisma ใช้ Schema-first approach มี Type Safety 100% และมี Developer Experience ที่ยอดเยี่ยม ทำให้การทำงานกับ Database เป็นเรื่องง่ายและปลอดภัย
บทความนี้จะสอน Prisma ORM ครบทุกเรื่อง ตั้งแต่ Schema Definition, Prisma Client, Migration, Relations, Performance Optimization ไปจนถึงการใช้งานจริงกับ Next.js, Express และ NestJS
Prisma คืออะไร?
Prisma คือ Open-Source Database Toolkit สำหรับ TypeScript/Node.js ที่ประกอบด้วย 3 ส่วนหลัก:
- Prisma Client — Auto-generated Type-safe Query Builder
- Prisma Migrate — Declarative Database Migration System
- Prisma Studio — Visual Database Browser (GUI)
Prisma รองรับ Database หลายตัว ได้แก่ PostgreSQL, MySQL, SQLite, SQL Server, MongoDB และ CockroachDB
Prisma vs ORM อื่น
| Feature | Prisma | Sequelize | TypeORM | Drizzle |
|---|---|---|---|---|
| Type Safety | Full (auto-gen) | Manual | Decorator-based | Full (schema) |
| Migration | Schema-first | Code-first | Both | Code-first |
| Query Style | Object API | Method chain | Active Record / Data Mapper | SQL-like builder |
| Learning Curve | ง่าย | ปานกลาง | สูง | ปานกลาง |
| Performance | ดี (Rust engine) | ปานกลาง | ปานกลาง | ดีมาก (thin layer) |
| Ecosystem | ใหญ่มาก | ใหญ่ | ใหญ่ | กำลังโต |
เริ่มต้นใช้ Prisma
# สร้างโปรเจกต์ใหม่
mkdir my-prisma-app && cd my-prisma-app
npm init -y
npm install prisma --save-dev
npm install @prisma/client
# Initialize Prisma
npx prisma init
# จะสร้างไฟล์:
# prisma/schema.prisma — Schema definition
# .env — DATABASE_URL
schema.prisma — หัวใจของ Prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
role Role @default(USER)
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
@@map("users")
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
tags Tag[]
createdAt DateTime @default(now())
@@index([authorId])
@@map("posts")
}
model Profile {
id Int @id @default(autoincrement())
bio String?
avatar String?
user User @relation(fields: [userId], references: [id])
userId Int @unique
@@map("profiles")
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
@@map("tags")
}
enum Role {
USER
ADMIN
MODERATOR
}
@@map เพื่อกำหนดชื่อ Table จริงใน Database, ใช้ @unique สำหรับ Unique constraint, และ @@index สำหรับ Index เพื่อ Performance
Relations ใน Prisma
One-to-One
// User มี Profile ได้ 1 อัน
model User {
id Int @id @default(autoincrement())
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int @unique // @unique ทำให้เป็น 1-to-1
}
One-to-Many
// User มีหลาย Post
model User {
id Int @id @default(autoincrement())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id])
authorId Int
}
Many-to-Many (Implicit)
// Post มีหลาย Tag, Tag มีหลาย Post
model Post {
id Int @id @default(autoincrement())
tags Tag[]
}
model Tag {
id Int @id @default(autoincrement())
posts Post[]
}
// Prisma สร้าง join table _PostToTag ให้อัตโนมัติ
Prisma Migrate
# สร้าง Migration จาก Schema
npx prisma migrate dev --name init
# → สร้างไฟล์ SQL ใน prisma/migrations/
# → Apply migration to DB
# → Generate Prisma Client
# Deploy Migration (Production)
npx prisma migrate deploy
# Reset Database (ลบข้อมูลทั้งหมด)
npx prisma migrate reset
# ดูสถานะ Migration
npx prisma migrate status
# สร้าง Migration แต่ยังไม่ Apply
npx prisma migrate dev --create-only
Prisma Client — CRUD Operations
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// CREATE
const user = await prisma.user.create({
data: {
email: 'john@example.com',
name: 'John',
role: 'USER',
profile: {
create: { bio: 'Hello World' }
}
},
include: { profile: true }
})
// READ — findMany
const users = await prisma.user.findMany({
where: {
role: 'ADMIN',
email: { contains: '@company.com' }
},
include: { posts: true },
orderBy: { createdAt: 'desc' },
take: 10,
skip: 0
})
// READ — findUnique
const user = await prisma.user.findUnique({
where: { email: 'john@example.com' },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' }
},
profile: true
}
})
// UPDATE
const updated = await prisma.user.update({
where: { id: 1 },
data: { name: 'John Updated' }
})
// DELETE
const deleted = await prisma.user.delete({
where: { id: 1 }
})
// UPSERT (update or create)
const upserted = await prisma.user.upsert({
where: { email: 'john@example.com' },
update: { name: 'John Updated' },
create: { email: 'john@example.com', name: 'John' }
})
Filtering และ Sorting
// Filtering operators
const results = await prisma.user.findMany({
where: {
AND: [
{ role: 'USER' },
{ email: { endsWith: '@gmail.com' } }
],
OR: [
{ name: { contains: 'john' } },
{ name: { startsWith: 'J' } }
],
NOT: { email: { contains: 'spam' } },
createdAt: {
gte: new Date('2026-01-01'),
lt: new Date('2026-04-01')
},
posts: {
some: { published: true } // มี post ที่ published อย่างน้อย 1
}
},
orderBy: [
{ role: 'asc' },
{ createdAt: 'desc' }
]
})
Pagination
Offset Pagination
// หน้าละ 20 รายการ
const page = 1
const pageSize = 20
const [users, total] = await prisma.$transaction([
prisma.user.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { id: 'asc' }
}),
prisma.user.count()
])
const totalPages = Math.ceil(total / pageSize)
Cursor-based Pagination (ดีกว่าสำหรับข้อมูลเยอะ)
const firstPage = await prisma.user.findMany({
take: 20,
orderBy: { id: 'asc' }
})
// หน้าถัดไป
const nextPage = await prisma.user.findMany({
take: 20,
skip: 1, // skip cursor item
cursor: { id: firstPage[firstPage.length - 1].id },
orderBy: { id: 'asc' }
})
Aggregation และ Grouping
// Aggregate
const stats = await prisma.post.aggregate({
_count: { id: true },
_avg: { views: true },
_max: { views: true },
_min: { views: true },
_sum: { views: true },
where: { published: true }
})
// Group By
const postsByRole = await prisma.user.groupBy({
by: ['role'],
_count: { id: true },
_avg: { age: true },
orderBy: { _count: { id: 'desc' } }
})
Raw SQL กับ Prisma
// Raw query
const users = await prisma.$queryRaw`
SELECT u.*, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON p."authorId" = u.id
GROUP BY u.id
HAVING COUNT(p.id) > ${5}
ORDER BY post_count DESC
`
// Raw execute (INSERT, UPDATE, DELETE)
const result = await prisma.$executeRaw`
UPDATE users SET name = ${newName} WHERE id = ${userId}
`
Seeding Database
// prisma/seed.ts
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
const admin = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin',
role: 'ADMIN',
posts: {
create: [
{ title: 'Welcome Post', published: true },
{ title: 'Draft Post', published: false }
]
}
}
})
console.log({ admin })
}
main()
.catch(e => { throw e })
.finally(() => prisma.$disconnect())
// package.json
{
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}
// Run seed
npx prisma db seed
Prisma กับ Next.js
// lib/prisma.ts — Singleton pattern สำหรับ Next.js
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production')
globalForPrisma.prisma = prisma
// app/api/users/route.ts (App Router)
import { prisma } from '@/lib/prisma'
import { NextResponse } from 'next/server'
export async function GET() {
const users = await prisma.user.findMany({
include: { posts: { where: { published: true } } }
})
return NextResponse.json(users)
}
export async function POST(request: Request) {
const body = await request.json()
const user = await prisma.user.create({
data: { email: body.email, name: body.name }
})
return NextResponse.json(user, { status: 201 })
}
Prisma กับ Express
import express from 'express'
import { PrismaClient } from '@prisma/client'
const app = express()
const prisma = new PrismaClient()
app.use(express.json())
app.get('/api/users', async (req, res) => {
const users = await prisma.user.findMany()
res.json(users)
})
app.post('/api/users', async (req, res) => {
try {
const user = await prisma.user.create({ data: req.body })
res.status(201).json(user)
} catch (e) {
if (e.code === 'P2002') {
res.status(409).json({ error: 'Email already exists' })
} else {
res.status(500).json({ error: 'Internal server error' })
}
}
})
// Graceful shutdown
process.on('beforeExit', () => prisma.$disconnect())
app.listen(3000)
Prisma กับ NestJS
// prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'
@Injectable()
export class PrismaService extends PrismaClient
implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect()
}
async onModuleDestroy() {
await this.$disconnect()
}
}
// users.service.ts
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
findAll() {
return this.prisma.user.findMany({ include: { posts: true } })
}
findOne(id: number) {
return this.prisma.user.findUnique({ where: { id } })
}
create(data: Prisma.UserCreateInput) {
return this.prisma.user.create({ data })
}
}
Connection Pooling (PgBouncer)
# .env สำหรับ Production กับ PgBouncer
DATABASE_URL="postgresql://user:pass@pgbouncer-host:6432/mydb?pgbouncer=true"
# สำหรับ Migrations (ต้องเชื่อมตรง ไม่ผ่าน PgBouncer)
DIRECT_URL="postgresql://user:pass@db-host:5432/mydb"
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
Performance Optimization
Select เฉพาะ Field ที่ต้องการ
// แทนที่จะดึงทุก field
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true
// ไม่ดึง field อื่นที่ไม่จำเป็น
}
})
Include vs Select
// include — ดึงทุก field + relation
const user = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true } // ดึงทุก field ของ user + posts
})
// select — ดึงเฉพาะที่ระบุ
const user = await prisma.user.findUnique({
where: { id: 1 },
select: {
name: true,
posts: {
select: { title: true, published: true }
}
}
})
Batch Operations
// createMany — เร็วกว่า create ทีละตัว
await prisma.user.createMany({
data: [
{ email: 'a@test.com', name: 'A' },
{ email: 'b@test.com', name: 'B' },
{ email: 'c@test.com', name: 'C' },
],
skipDuplicates: true
})
// Transaction — หลาย operations ใน 1 transaction
const [updatedUser, newPost] = await prisma.$transaction([
prisma.user.update({ where: { id: 1 }, data: { name: 'New' } }),
prisma.post.create({ data: { title: 'Post', authorId: 1 } })
])
Prisma Accelerate และ Pulse
Prisma Accelerate คือ Connection Pooling + Global Caching ที่ทำงานบน Edge:
- Connection pooling สำหรับ Serverless
- Global cache ลด database load
- ใช้งานง่าย เปลี่ยนแค่ DATABASE_URL
Prisma Pulse คือ Real-time Database Event Streaming:
- Subscribe to database changes
- React to INSERT, UPDATE, DELETE events
- ใช้แทน manual polling
// Prisma Pulse example
const stream = await prisma.user.stream()
for await (const event of stream) {
console.log('Change detected:', event)
// { action: 'create', created: { id: 1, name: 'John' } }
}
Testing กับ Prisma
// ใช้ Database แยกสำหรับ Test
// .env.test
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb_test"
// test setup
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
beforeEach(async () => {
// ล้างข้อมูลก่อน test
await prisma.$transaction([
prisma.post.deleteMany(),
prisma.user.deleteMany(),
])
})
afterAll(async () => {
await prisma.$disconnect()
})
// test example
test('should create user', async () => {
const user = await prisma.user.create({
data: { email: 'test@test.com', name: 'Test' }
})
expect(user.email).toBe('test@test.com')
expect(user.id).toBeDefined()
})
Prisma in Production Checklist
| หัวข้อ | สิ่งที่ต้องทำ |
|---|---|
| Migration | ใช้ prisma migrate deploy ไม่ใช่ dev |
| Connection Pool | ตั้ง connection limit เหมาะสม |
| Error Handling | จัดการ Prisma error codes (P2002, P2025, etc.) |
| Logging | เปิด query logging ใน production |
| Monitoring | ติดตาม slow queries |
| Backup | มี backup strategy สำหรับ database |
| Security | ใช้ parameterized queries (Prisma ทำให้อัตโนมัติ) |
// Production PrismaClient setup
const prisma = new PrismaClient({
log: [
{ level: 'query', emit: 'event' },
{ level: 'error', emit: 'stdout' },
{ level: 'warn', emit: 'stdout' },
],
datasources: {
db: { url: process.env.DATABASE_URL }
}
})
// Log slow queries
prisma.$on('query', (e) => {
if (e.duration > 1000) {
console.warn(`Slow query (${e.duration}ms): ${e.query}`)
}
})
สรุป
Prisma เป็น ORM ที่ดีที่สุดสำหรับ TypeScript/Node.js ในปี 2026 ด้วย Type Safety ที่แข็งแกร่ง, Schema-first approach ที่เข้าใจง่าย, และ Developer Experience ที่ยอดเยี่ยม ไม่ว่าจะใช้กับ Next.js, Express หรือ NestJS Prisma ก็ทำให้การจัดการ Database เป็นเรื่องง่าย
เริ่มต้นวันนี้ด้วย npx prisma init สร้าง Schema แรก เชื่อมต่อ Database และลองใช้ Prisma Client — คุณจะเข้าใจว่าทำไม Developer ทั่วโลกถึงเปลี่ยนมาใช้ Prisma แทน ORM แบบเดิม
