Home > Blog > tech

Prisma ORM คืออะไร? สอน Database Toolkit สำหรับ TypeScript/Node.js Developer 2026

prisma orm database toolkit guide
Prisma ORM Database Toolkit Guide 2026
2026-04-10 | tech | 3800 words

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 รองรับ Database หลายตัว ได้แก่ PostgreSQL, MySQL, SQLite, SQL Server, MongoDB และ CockroachDB

Prisma vs ORM อื่น

FeaturePrismaSequelizeTypeORMDrizzle
Type SafetyFull (auto-gen)ManualDecorator-basedFull (schema)
MigrationSchema-firstCode-firstBothCode-first
Query StyleObject APIMethod chainActive Record / Data MapperSQL-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
}
Schema Tips: ใช้ @@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}
`
ใช้ Raw SQL เมื่อ: Query ซับซ้อนมากเกินไปสำหรับ Prisma Client API เช่น Complex JOIN, Window Functions, หรือ Database-specific features

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:

Prisma Pulse คือ Real-time Database Event Streaming:

// 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 แบบเดิม


Back to Blog | iCafe Forex | SiamLanCard | Siam2R