Prisma
Convention sử dụng Prisma trong Next.js: client singleton, schema, migrations, transaction và soft delete. Toàn bộ truy vấn DB thực hiện trong Service layer, không gọi Prisma trực tiếp từ Actions.
Prisma Client
Singleton: một instance dùng chung
Trong môi trường Next.js (và serverless), không tạo new PrismaClient() nhiều lần mỗi request — dễ vượt quá giới hạn kết nối DB. Dùng singleton trong configs/prisma.ts (hoặc tương đương).
import { PrismaClient } from "@prisma/client";
import { ITXClientDenyList } from "@prisma/client/runtime/library";
import { __DEV__ } from "./environment";
export type PrismaClientTransaction = Omit<PrismaClient, ITXClientDenyList>;
export const prisma = new PrismaClient({
log: __DEV__ ? ["info", "warn", "error"] : ["error"],
});
Chỉ dùng trong Services
Mọi truy vấn thực hiện trong Services; Action gọi Service. Xem Kiến trúc & các tầng.
Schema (schema.prisma)
Tên model: PascalCase, số ít (Singular)
- Model đặt tên PascalCase, số ít:
Customer,OrderItem,Invoice, không dùngCustomers,OrderItems.
model Customer {
id String @id @default(cuid())
name String
email String? @unique
}
model OrderItem {
id String @id @default(cuid())
orderId String
quantity Int
order Order @relation(fields: [orderId], references: [id])
}
Trường bắt buộc vs optional
- Trường bắt buộc trong nghiệp vụ: khai báo không có
?(non-null). - Trường optional: dùng
?. Đồng bộ với Strapi/API (xem Strapi Schema).
model Customer {
id String @id @default(cuid())
name String // bắt buộc
email String? @unique
deletedAt DateTime? // soft delete
}
Quan hệ (relations)
- Đặt tên relation rõ ràng; dùng
@relation(fields: [foreignKey], references: [id])và khai báo@relation(name: "...")khi có nhiều quan hệ giữa hai model. - Tránh vòng tròn phụ thuộc; ưu tiên chiều từ “nhiều” về “một” (ví dụ
OrderItem.orderId→Order).
model Order {
id String @id @default(cuid())
items OrderItem[]
}
model OrderItem {
id String @id @default(cuid())
orderId String
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
}
Soft delete: dùng deletedAt (hoặc publishedAt)
- Không dùng hard delete (
delete) cho dữ liệu nghiệp vụ. Dùng soft delete: cập nhật trường đánh dấu đã xóa. - Trường thường dùng:
publishedAt(DateTime?) theo chuẩn Strapi. Mặc định filterdeletedAt: null/publishedAt: { not: null }khi list.
model Customer {
id String @id @default(cuid())
name String
publishedAt DateTime?
}
- Trong Service: update
deletedAt = nullthay vìprisma.customer.delete(...).
Transaction
Nhiều thao tác DB atomic: dùng $transaction
Khi một nghiệp vụ cần nhiều thao tác DB và phải atomic (hoặc tất cả thành công, hoặc tất cả rollback), dùng prisma.$transaction. Action gọi một Service “orchestrator” hoặc Service gọi Service con với tx.
// services/order.ts
import type { Prisma } from '@prisma/client';
export async function createOrderWithItemsService(tx: Prisma.TransactionClient, input: CreateOrderInput) {
const order = await tx.order.create({ data: { ... } });
await tx.orderItem.createMany({ data: input.items.map(...) });
await updateInventoryService(tx, input.items);
return order;
}
export async function updateInventoryService(
tx: Prisma.TransactionClient,
items: { productId: string; qty: number }[]
) {
// ...
}
Select / Include
Chỉ lấy field cần thiết
- Tránh
select: undefined/includetoàn bộ relation khi không cần. Dùngselecthoặcincludecụ thể để giảm dữ liệu và rõ contract.
// tốt: chỉ field cần dùng
const customer = await prisma.customer.findUnique({
where: { id },
select: { id: true, name: true, email: true },
});
// tránh: lấy hết khi chỉ cần vài field
const customer = await prisma.customer.findUnique({ where: { id } });
Tóm tắt
| Nội dung | Convention |
|---|---|
| Client | Singleton trong configs/prisma.ts |
| Model name | PascalCase, singular |
| Soft delete | publishedAt, update thay vì delete |
| Transaction | Dùng prisma.$transaction khi thực hiện nhiều nhiều thao tác DB |
| Select/Include | Chỉ định rõ field/relation cần dùng |