Middleware trong Mongoose

Phùng Hùng

Phùng Hùng

Teacher and Programmer

Các kiểu middleware

Mongoose có 4 kiểu middleware, ta phân biệt chúng bằng giá trị của this và các phương thức ta có thể áp dụng:

  • Document Middleware

    • this là document hiện tại.
    • Các phương thức sau của document có thể áp dụng được middleware này:
      • validate
      • save
      • remove
      • updateOne
      • deleteOne
  • Query Middleware

    • this là query
    • Các phương thức sau của query và model được hỗ trợ:
      • count
      • deleteMany
      • deleteOne
      • find
      • findOne
      • findOneAndDelete
      • findOneAndRemove
      • findOneAndUpdate
      • remove
      • update
      • updateOne
      • updateMany
  • Model Middleware

    • this là model
    • chỉ phương thức sau của model được hỗ trợ:
      • insertMany
  • Aggregate Middleware

    • this là aggregation object.
    • chạy khi bạn gọi exec() trên aggregate object.

Pre Middleware

Pre Middleware là middleware chạy trước khi chạy phương thức nó hỗ trợ, có thể áp dụng nhiều pre middleware, middleware đặt trước sẽ chạy trước. Điều kiện là trong mỗi middleware ta phải chạy next.

Hàm middleware phải là hàm thông thường, không được là hàm mũi tên và trong đó phải gọi next().

Pre middleware khai báo thông qua schema:

const schema = new Schema({...});
schema.pre('save', function(next) {
// do stuff
next();
});

Khi middleware là hàm async, bạn không cần gọi next thủ công nữa. Bởi vì Mongoose tự động gọi next khi middleware trả về một promise.

schema.pre('save', async function () {
await doStuff();
await doMoreStuff();
});

Khi có lỗi bạn có thể tạo ra lỗi và truyền vào next, một đối tượng error cũng đươc truyền vào ở đối số thứ nhất:

schema.pre('save', function (error, next) {
const err = new Error('something went wrong');
// If you call `next()` with an argument, that argument is assumed to be
// an error.
next(err);
});

Trong hàm async bạn chỉ cần throw lỗi này:

schema.pre('save', async function (error) {
await Promise.resolve();
// You can also throw an error in an `async` function
throw new Error('something went wrong');
});

Bạn có thể truyên thêm đối số vào middleware, khi đó đối số cuối cùng sẽ là next.

Post middleware

Post middleware chạy sau phương thức nó hỗ trợ và sau khi mọi pre middleware đã hoàn thành:

schema.post('init', function (doc) {
console.log('%s has been initialized from the db', doc._id);
});
schema.post('validate', function (doc) {
console.log('%s has been validated (but not saved yet)', doc._id);
});
schema.post('save', function (doc) {
console.log('%s has been saved', doc._id);
});
schema.post('remove', function (doc) {
console.log('%s has been removed', doc._id);
});

Phải định nghĩa middleware trước khi tạo model

Thường ta không thể định nghĩa middleware sau khi tạo model.

const schema = new mongoose.Schema({ name: String });
// tạo model từ Schema
const User = mongoose.model('User', schema);
// Định nghĩa middleware
schema.pre('save', () => console.log('Hello from pre save'));
new User({ name: 'test' }).save(); // không làm việc

Phương thức save/validate

save() sẽ luôn trigger validate() bởi mongoose có sẵn một middleware pre('save') trong đó có gọi validate(). Điêu đó nghĩa là mọi middleware pre('validate')post('validate') luôn chạy trước pre('save').

schema.pre('validate', function () {
console.log('this gets printed first');
});
schema.post('validate', function () {
console.log('this gets printed second');
});
schema.pre('save', function () {
console.log('this gets printed third');
});
schema.post('save', function () {
console.log('this gets printed fourth');
});

Xung đột tên

Có hai middleware cùng áp dụng cho phương thức remove.

  • Document middleware
  • Query middleware

Khi đã sử dụng một middleware thì middleware kia không chạy:

schema.pre('remove', function () {
console.log('Removing!');
});
// Prints "Removing!"
doc.remove();
// Does **not** print "Removing!". Query middleware for `remove` is not
// executed by default.
Model.remove();

Do vậy, để khai báo riêng cho từng loại middleware ta cần truyền thêm tùy chọn { document: true } hoặc { query: true }:

// Only document middleware
schema.pre('remove', { document: true }, function () {
console.log('Removing doc!');
});
// Only query middleware. This will get called when you do `Model.remove()`
// but not `doc.remove()`.
schema.pre('remove', { query: true }, function () {
console.log('Removing!');
});