Sửa lỗi BSONTypeError: Argument passed in must be a string of 12 bytes or 24 hex characters khi tạo ObjectId

beginner🍃 MongoDB2026-05-31| Node.js với gói npm mongodb (v4+) hoặc mongoose, BSON v4+, MongoDB 5.x / 6.x / 7.x

Error Message

BSONTypeError: Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer
#mongodb#bson#objectid#nodejs

Lỗi gặp phải

BSONTypeError: Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer

Lỗi này xuất hiện khi bạn truyền một giá trị không hợp lệ vào new ObjectId(). Các nguyên nhân thường gặp: undefined, chuỗi rỗng, hoặc ID bị sai định dạng đến từ input của người dùng hay một URL param không hợp lệ.

Nguyên nhân gây ra lỗi

MongoDB ObjectId có đúng 24 ký tự hex (ví dụ: 507f1f77bcf86cd799439011) hoặc 12 byte thô. BSON rất nghiêm ngặt về điều này — truyền bất kỳ giá trị nào khác là nó ném lỗi ngay lập tức, không có kiểm tra một phần.

Các nguyên nhân phổ biến nhất:

  • Route param bị undefined vì tên param trong route không khớp với tên bạn đang đọc
  • Client gửi lên một chuỗi sai hoặc một ID ngắn thay vì ObjectId thực sự
  • Chuỗi ObjectId bị cắt bớt trong quá trình truyền — chỉ thiếu một ký tự là đã hỏng
  • Import ObjectID (chữ D viết hoa, kiểu mongodb@3) trong dự án dùng mongodb@4+ — âm thầm trả về undefined
  • Truyền _id của document vốn đã là ObjectId instance vào new ObjectId() — thừa thãi, dù thường không gây lỗi

Cách sửa từng bước

1. Luôn kiểm tra trước khi tạo ObjectId

BSON có sẵn ObjectId.isValid() để làm chính xác điều này. Hãy gọi nó mỗi khi ID đến từ bên ngoài code của bạn — route param, request body, query string, tất cả đều phải kiểm tra.

const { ObjectId } = require('mongodb'); // or from 'bson'

// KHÔNG NÊN — sẽ nổ nếu id là undefined hoặc sai định dạng
const id = new ObjectId(req.params.id);

// NÊN — kiểm tra trước
const rawId = req.params.id;
if (!ObjectId.isValid(rawId)) {
  return res.status(400).json({ error: 'Invalid ID format' });
}
const id = new ObjectId(rawId);

2. Kiểm tra cách import — ObjectId hay ObjectID

Trong mongodb@3, export là ObjectID (chữ D viết hoa). Từ mongodb@4 trở đi, đổi thành ObjectId (chữ d viết thường). Import sai tên là bạn nhận về undefined. Rồi new undefined() sẽ báo lỗi ngay.

// mongodb@4+ (hiện tại)
const { ObjectId } = require('mongodb');

// ESM
import { ObjectId } from 'mongodb';

// Mongoose
const { Types } = require('mongoose');
const id = new Types.ObjectId(rawId);

// Vẫn dùng mongodb@3? Chữ D viết hoa:
const { ObjectID } = require('mongodb');

3. Sửa lỗi "sai tên param" kinh điển

Route của bạn là /api/users/:userId nhưng bạn lại đọc req.params.id. Kết quả: undefined. Lỗi này rất hay gặp — hãy kiểm tra kỹ để tên param khớp nhau từ đầu đến cuối.

// Route: GET /api/users/:userId
router.get('/api/users/:userId', async (req, res) => {
  const { userId } = req.params; // KHÔNG PHẢI req.params.id

  if (!userId || !ObjectId.isValid(userId)) {
    return res.status(400).json({ error: 'Invalid or missing user ID' });
  }

  const user = await db.collection('users').findOne({ _id: new ObjectId(userId) });
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.json(user);
});

4. Bọc trong try-catch cho các thao tác hàng loạt

Xử lý 50 ID trong một batch? Một entry lỗi không nên làm hỏng toàn bộ request. Một helper nhỏ lọc dữ liệu rác và tiếp tục xử lý:

function toObjectId(id) {
  try {
    return new ObjectId(id);
  } catch {
    return null;
  }
}

const ids = rawIds.map(toObjectId).filter(Boolean);
const results = await db.collection('items').find({ _id: { $in: ids } }).toArray();

5. Chú ý quirk của isValid() với chuỗi 12 ký tự

ObjectId.isValid() trả về true với bất kỳ chuỗi 12 ký tự nào — không chỉ hex. Thử xem: ObjectId.isValid("hello world!") trả về true. Với các ID hiển thị cho người dùng mà bạn cần kiểm tra chặt 24 ký tự hex, hãy thêm regex:

function isStrictObjectId(id) {
  return typeof id === 'string' && /^[a-f\d]{24}$/i.test(id);
}

// Sử dụng
if (!isStrictObjectId(req.params.id)) {
  return res.status(400).json({ error: 'Invalid ID' });
}

Xác nhận đã sửa xong

Chạy đoạn kiểm tra nhanh này — mỗi output phải khớp với comment bên cạnh:

const { ObjectId } = require('mongodb');

// Hợp lệ — phải hoạt động được
console.log(ObjectId.isValid('507f1f77bcf86cd799439011')); // true
console.log(new ObjectId('507f1f77bcf86cd799439011').toString()); // '507f1f77bcf86cd799439011'

// Không hợp lệ — isValid() bắt được trước khi ObjectId() chạy
console.log(ObjectId.isValid(undefined));       // false
console.log(ObjectId.isValid(''));              // false
console.log(ObjectId.isValid('123'));           // false
console.log(ObjectId.isValid('not-hex-data')); // false

Cả sáu output đều đúng? Guard của bạn đã chắc chắn rồi.

Lưu ý thêm

  • Tập trung logic kiểm tra vào một chỗ: Viết một helper isValidObjectId() trong file utils dùng chung. Import một lần ở khắp nơi còn hơn copy-paste cùng ba dòng đó qua hàng chục route handler.
  • Kiểm tra ngay tại điểm tiếp nhận: Validate ID trong route handler, đừng chôn vùi bên trong các service function. Bắt lỗi sớm đồng nghĩa với thông báo lỗi rõ ràng hơn và thời gian debug giảm đi một nửa.
  • Log giá trị sai: Khi kiểm tra thất bại, hãy log chính xác giá trị nhận được — console.error('Invalid ObjectId:', rawId). Bạn sẽ tự cảm ơn mình khi lần đầu tiên client gửi lên chuỗi "undefined" theo nghĩa đen.
  • Mongoose CastError: Truyền ID không hợp lệ vào Model.findById(id) thì Mongoose ném CastError, không phải BSONTypeError. Tên lỗi khác nhau, nguyên nhân gốc giống nhau — hãy validate trước khi query trong mọi trường hợp.
  • Đừng bọc lại ObjectId đã tồn tại: Nếu doc._id đã là ObjectId instance rồi, new ObjectId(doc._id) về mặt kỹ thuật vẫn hoạt động nhưng thêm overhead vô ích. Hãy dùng trực tiếp instance đó trong các query.

Related Error Notes