Prisma ORM — Từ Schema Đến Database Trong Phút
Làm việc với database qua Prisma ORM: schema, migrations, queries, relations và best practices.
Prisma là ORM hiện đại cho TypeScript. Không như TypeORM hay Sequelize — Prisma tự động sinh type, giúp DevX cực kỳ tốt.
Setup#
npm install prisma @prisma/client
npx prisma initbashTạo file 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
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
}prismaMigrations#
# Tạo migration
npx prisma migrate dev --name init
# Áp dụng migration lên production
npx prisma migrate deploy
# Reset database
npx prisma migrate reset
# Xem migration history
npx prisma migrate statusbashCRUD Cơ Bản#
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Create
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
},
});
// Read
const user = await prisma.user.findUnique({
where: { email: 'alice@example.com' },
include: { posts: true },
});
const users = await prisma.user.findMany({
where: { name: { contains: 'Ali' } },
orderBy: { createdAt: 'desc' },
take: 10,
skip: 0,
});
// Update
const updated = await prisma.user.update({
where: { id: 1 },
data: { name: 'Alice Updated' },
});
// Delete
await prisma.user.delete({ where: { id: 1 } });typescriptRelations#
Include — Load Relation#
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 5,
},
},
});typescriptNested Create#
const user = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob',
posts: {
create: [
{ title: 'Post 1', content: 'Hello' },
{ title: 'Post 2', content: 'World' },
],
},
},
include: { posts: true },
});typescriptNested Update#
await prisma.user.update({
where: { id: 1 },
data: {
posts: {
update: {
where: { id: 10 },
data: { title: 'Updated Title' },
},
create: { title: 'New Post', content: 'Content' },
delete: { id: 5 },
},
},
});typescriptTransactions#
// Interactive transaction
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data: { email, name } });
const profile = await tx.profile.create({
data: { userId: user.id, bio },
});
return { user, profile };
});
// Batch transaction
const [user1, user2] = await prisma.$transaction([
prisma.user.create({ data: { email: 'a@test.com', name: 'A' } }),
prisma.user.create({ data: { email: 'b@test.com', name: 'B' } }),
]);typescriptPagination#
// Offset pagination
const posts = await prisma.post.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
});
// Cursor pagination (nhanh hơn cho large dataset)
const posts = await prisma.post.findMany({
take: limit + 1, // Lấy dư 1 để biết có next page
cursor: cursor ? { id: cursor } : undefined,
orderBy: { id: 'asc' },
where: { published: true },
});typescriptAggregation#
const stats = await prisma.post.aggregate({
_count: { id: true },
_avg: { viewCount: true },
_max: { viewCount: true },
where: { published: true },
});
const group = await prisma.post.groupBy({
by: ['authorId'],
_count: { id: true },
_sum: { viewCount: true },
orderBy: { _sum: { viewCount: 'desc' } },
});typescriptMiddleware — Hook#
// Log query
prisma.$use(async (params, next) => {
const before = Date.now();
const result = await next(params);
const after = Date.now();
console.log(`Query ${params.model}.${params.action} took ${after - before}ms`);
return result;
});
// Soft delete
prisma.$use(async (params, next) => {
if (params.action === 'delete') {
params.action = 'update';
params.args.data = { deletedAt: new Date() };
}
return next(params);
});typescriptRaw Query#
// Khi Prisma query không đủ
const users = await prisma.$queryRaw`
SELECT id, name, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON p.author_id = u.id
GROUP BY u.id
HAVING COUNT(p.id) > 5
`;
// Execute raw
await prisma.$executeRaw`UPDATE posts SET view_count = view_count + 1 WHERE id = ${postId}`;typescriptBest Practices#
// 1. Singleton PrismaClient
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;
// 2. Select chỉ field cần — tránh over-fetching
const users = await prisma.user.findMany({
select: { id: true, name: true, email: true },
});
// 3. Batch create với createMany
await prisma.post.createMany({
data: posts,
skipDuplicates: true,
});
// 4. Upsert
await prisma.user.upsert({
where: { email: 'alice@example.com' },
update: { name: 'Alice Updated' },
create: { email: 'alice@example.com', name: 'Alice' },
});typescriptMigration Production#
# Không dùng migrate dev trên production
# Dùng migrate deploy — chỉ chạy migration mới
npx prisma migrate deploy
# Nếu cần chỉnh sửa migration đã áp dụng
npx prisma migrate resolve --applied 20240718000000_initbashKết Luận#
Prisma giúp làm việc với database dễ hơn rất nhiều:
- Không viết SQL (trừ khi cần)
- Type-safe từ đầu đến cuối
- Migration tự động
- Relations xử lý dễ dàng
- DevX xuất sắc
Nhớ: Luôn dùng select để tránh over-fetching. Transaction cho multi-step operations. Migration deploy cho production.