0

8 Mẹo hàng đầu khi sử dụng Prisma với MongoDB

Hôm nay, mình sẽ chia sẻ 8 mẹo quan trọng nhất khi sử dụng Prisma ORM với MongoDB. Nếu bạn đang xây dựng ứng dụng với MongoDB và muốn dùng Prisma làm ORM, có một số điểm quan trọng bạn cần lưu ý. MongoDB là cơ sở dữ liệu dạng tài liệu (document-based), hoạt động khác với các cơ sở dữ liệu quan hệ, và Prisma có những cách xử lý đặc thù cho những khác biệt này.

Mình đã làm việc nhiều năm với cả MongoDB và Prisma ORM, và đã tổng hợp lại những mẹo cốt lõi giúp bạn tiết kiệm hàng giờ gỡ lỗi và xây dựng ứng dụng hiệu quả hơn. Đây không chỉ là hướng dẫn cài đặt cơ bản — mà là những hiểu biết thực tế tạo ra sự khác biệt giữa một ứng dụng gặp trục trặc và một ứng dụng chạy mượt mà trong môi trường production.

Mẹo #1: Cấu hình MongoDB với replica set

MongoDB khi dùng với Prisma ORM phải được cấu hình thành một replica set. Đây không phải là tuỳ chọn — mà là bắt buộc. Prisma sử dụng transaction nội bộ để tránh ghi thiếu khi thực hiện các truy vấn lồng nhau, và MongoDB chỉ cho phép transaction trong môi trường replica set.

Nếu bạn cố dùng Prisma ORM với MongoDB dạng standalone (độc lập), bạn sẽ gặp lỗi: "Transactions are not supported by this deployment." — kể cả với thao tác đơn giản, vì Prisma bọc mọi thao tác trong transaction.

Giải pháp dễ nhất là sử dụng MongoDB Atlas, vốn hỗ trợ replica set mặc định (ngay cả gói miễn phí). Nếu phát triển local, bạn có thể chuyển MongoDB standalone sang replica set theo hướng dẫn trên trang chủ MongoDB.

Mẹo #2: Làm việc với các trường ObjectId

Trong MongoDB, khóa chính (primary key) thường là ObjectId nằm trong trường _id. Khi làm việc với Prisma ORM, bạn cần ánh xạ chính xác trường này trong schema:

Trường ObjectId trong Prisma phải là kiểu String hoặc Bytes

Phải có @db.ObjectId

Nên dùng @default(auto()) để tự sinh ID

Ví dụ mô hình với ObjectId:

model User {
  id    String @id @default(auto()) @map("_id") @db.ObjectId
  email String
  name  String?
}

Khi cần tạo ObjectId thủ công để test, dùng package bson:

import { ObjectId } from 'bson'
const id = new ObjectId()

Mẹo #3: Hiểu sự khác biệt giữa null và trường không tồn tại

MongoDB phân biệt rõ giữa các trường có giá trị null và những trường không tồn tại. Đây là một phần của tính năng "polymorphism" trong MongoDB — cho phép lưu nhiều kiểu dữ liệu khác nhau trong cùng một collection.

Ví dụ: Nếu bạn tạo bản ghi mà không truyền một trường tuỳ chọn, MongoDB sẽ không lưu trường đó. Nhưng Prisma ORM sẽ trả về giá trị null, khiến bạn tưởng rằng trường này có tồn tại và được gán null.

Khi lọc dữ liệu, nếu bạn chỉ lọc name: null, bạn chỉ tìm thấy các bản ghi mà name thực sự được set là null. Để bao gồm cả các bản ghi không có trường name, dùng toán tử isSet:

// Find records where name is either null or missing
const users = await prisma.user.findMany({
  where: {
    OR: [
      { name: null },
      { name: { isSet: false } }
    ]
  }
})

Mẹo #4: Xử lý quan hệ đúng cách

MongoDB xử lý quan hệ bằng cách tham chiếu tài liệu hoặc nhúng tài liệu, khác hoàn toàn với khoá ngoại trong cơ sở dữ liệu quan hệ. Khi introspect MongoDB, Prisma có thể không tự động nhận biết các quan hệ.

Bạn cần tự thêm trường quan hệ trong schema. Ví dụ:

model Post {
  id     String @id @default(auto()) @map("_id") @db.ObjectId
  title  String
  userId String @db.ObjectId
  user   User   @relation(fields: [userId], references: [id])
}

model User {
  id    String @id @default(auto()) @map("_id") @db.ObjectId
  email String
  posts Post[]
}

Nhớ rằng các khoá tham chiếu tới tài liệu khác luôn cần @db.ObjectId.

Mẹo #5: Mô hình hóa tài liệu nhúng bằng type

MongoDB cho phép nhúng dữ liệu có cấu trúc vào trong một tài liệu — giúp giảm nhu cầu "join". Prisma ORM hỗ trợ mô hình này bằng từ khóa type.

  • type trong Prisma không tạo collection riêng mà chỉ mô tả cấu trúc nhúng.

Ví dụ:

type Address {
  street  String
  city    String
  state   String
  zipCode String
}

model Customer {
  id        String   @id @default(auto()) @map("_id") @db.ObjectId
  name      String
  email     String   @unique
  address   Address? // Embedded document
  addresses Address[] // Array of embedded documents
}

Sử dụng trong code:

// Creating a record with embedded document
const customer = await prisma.customer.create({
  data: {
    name: 'Jane Smith',
    email: 'jane@example.com',
    address: {
      street: '123 Main St',
      city: 'Anytown',
      state: 'CA',
      zipCode: '12345'
    }
  }
})

// Querying with embedded fields
const californiaCustomers = await prisma.customer.findMany({
  where: {
    address: {
      state: 'CA'
    }
  }
})

Tài liệu nhúng thích hợp khi:

  • Luôn truy xuất cùng tài liệu cha
  • Có mối quan hệ sở hữu rõ ràng (chỉ thuộc về một tài liệu cha)
  • Không cần truy vấn độc lập

Mẹo #6: Quản lý schema đơn giản với MongoDB

MongoDB có schema linh hoạt nên không cần migrate phức tạp. Bạn có thể thay đổi mô hình dữ liệu mà không downtime.

Dùng lệnh prisma db push để:

  • Tạo collection nếu chưa có
  • Thiết lập index cho các trường @unique
  • Cập nhật Prisma Client tự động

Mẹo #7: Một số lưu ý khi thiết kế

Một vài điểm quan trọng:

  • MongoDB chỉ dùng một trường _id làm khoá chính (không có @@id)
  • Dùng auto() với ObjectId, không dùng autoincrement()
  • Các quan hệ có vòng lặp nên dùng onDelete: NoAction để tránh lỗi
  • Kiểu Decimal128 trong MongoDB mới chỉ được Prisma hỗ trợ một phần

Biết rõ sự khác biệt giữa document DB và RDBMS giúp bạn thiết kế ứng dụng hiệu quả hơn.

Mẹo #8: Tối ưu hoá với collection lớn

Hiệu suất MongoDB có thể giảm nếu bạn không tối ưu khi sử dụng Prisma ORM. Một số mẹo:

  • Thêm @index cho trường thường truy vấn
  • Dùng select để chỉ lấy trường cần thiết
  • Dùng skiptake để phân trang
  • Dùng $runCommandRaw cho các truy vấn phức tạp hoặc pipeline

Ví dụ:

const users = await prisma.user.findMany({
  where: { role: "ADMIN" },
  select: { id: true, email: true }, // Only select needed fields
  take: 100, // Limit to 100 results
  skip: page * 100, // Pagination
})

Kết luận

Tám mẹo trên sẽ giúp bạn dùng Prisma ORM với MongoDB hiệu quả hơn và tránh các lỗi phổ biến. Hãy nhớ rằng MongoDB là cơ sở dữ liệu dạng tài liệu, khác biệt hoàn toàn với SQL, và hiểu rõ sự khác biệt này là chìa khoá để xây dựng ứng dụng hiệu năng cao.

Bạn có thể xem hướng dẫn đầy đủ cách xây dựng full-stack app với Next.js + Prisma + MongoDB để hiểu thêm.

Tóm tắt: Luôn dùng replica set, xử lý ObjectId chính xác, phân biệt null và trường thiếu, xử lý quan hệ đúng, dùng db push, tận dụng mô hình tài liệu của MongoDB, và tối ưu hóa truy vấn khi làm việc với dữ liệu lớn.

Cảm ơn các bạn đã theo dõi!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí