Skip to main content

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ùng Customers, 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 filter deletedAt: null / publishedAt: { not: null } khi list.
model Customer {
id String @id @default(cuid())
name String
publishedAt DateTime?
}
  • Trong Service: update deletedAt = null thay 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 / include toàn bộ relation khi không cần. Dùng select hoặc include cụ 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 dungConvention
ClientSingleton trong configs/prisma.ts
Model namePascalCase, singular
Soft deletepublishedAt, update thay vì delete
TransactionDùng prisma.$transaction khi thực hiện nhiều nhiều thao tác DB
Select/IncludeChỉ định rõ field/relation cần dùng