You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js中Mongoose的remove()不是函数,级联删除训练营课程失败

问题:Mongoose调用bootcamp.remove()报错及级联删除实现方案

我用Node.js和Mongoose开发训练营(bootcamp)与课程(course)的API,二者为一对多关系,希望实现删除训练营时自动删除其关联的所有课程。

在Bootcamp模型中定义了pre('remove')中间件用于级联删除,同时配置了虚拟属性反向关联课程:

// Cascade delete courses when a bootcamp is deleted
BootcampSchema.pre('remove', async function(next) {
    console.log(`Courses being removed from bootcamp ${this._id}`);
    await this.model('Course').deleteMany({ bootcamp: this._id });
    next();
});

// Reverse populate with virtuals
BootcampSchema.virtual('courses', {
    ref: 'Course',
    localField: '_id',
    foreignField: 'bootcamp',
    justOne: false
});

控制器bootcamps.js中的删除逻辑如下:

// @desc    Delete a Bootcamp
// @route   DELETE /api/v1/bootcamps/:id
// @access  Private
exports.deleteBootcamp = asyncHandler(async (req, res, next) => {
        const bootcamp = await Bootcamp.findById(req.params.id);
        if(!bootcamp) {
            return next(new ErrorResponse(`Bootcamp not found with id of ${req.params.id}`, 404));
        }
        bootcamp.remove();
        res.status(200).json({ success: true, data: {} });
})

但通过Postman发送请求时返回错误:bootcamp.remove is not a function,想了解错误原因及其他级联删除方案。


错误原因

Mongoose从6.x版本开始,移除了文档实例的remove()方法,替换为deleteOne()或deleteMany()方法。你当前使用的Mongoose版本应该是6.x及以上,所以调用bootcamp.remove()会触发该错误。

修复当前方案

把控制器中的bootcamp.remove()替换为await bootcamp.deleteOne(),同时必须用await等待删除操作完成,否则中间件可能无法正常触发:

exports.deleteBootcamp = asyncHandler(async (req, res, next) => {
        const bootcamp = await Bootcamp.findById(req.params.id);
        if(!bootcamp) {
            return next(new ErrorResponse(`Bootcamp not found with id of ${req.params.id}`, 404));
        }
        // 替换为deleteOne()并添加await
        await bootcamp.deleteOne();
        res.status(200).json({ success: true, data: {} });
})

修改后,pre('remove')中间件会正常触发,自动删除关联的课程。


其他级联删除方案

1. 结合findByIdAndDelete与查询级中间件

如果不想先查询文档再删除,可以直接使用findByIdAndDelete方法,但需要将中间件从pre('remove')改为查询级的pre('deleteOne'),因为findByIdAndDelete会触发查询级的deleteOne钩子:

// 修改Bootcamp模型的中间件
BootcampSchema.pre('deleteOne', { document: false, query: true }, async function(next) {
    // 获取查询条件中的训练营ID
    const bootcampId = this.getQuery()._id;
    console.log(`Courses being removed from bootcamp ${bootcampId}`);
    await this.model('Course').deleteMany({ bootcamp: bootcampId });
    next();
});

// 控制器简化为:
exports.deleteBootcamp = asyncHandler(async (req, res, next) => {
        const bootcamp = await Bootcamp.findByIdAndDelete(req.params.id);
        if(!bootcamp) {
            return next(new ErrorResponse(`Bootcamp not found with id of ${req.params.id}`, 404));
        }
        res.status(200).json({ success: true, data: {} });
})

2. 手动实现级联删除

在控制器中先删除关联的课程,再删除训练营,不需要依赖Mongoose中间件:

exports.deleteBootcamp = asyncHandler(async (req, res, next) => {
        const bootcamp = await Bootcamp.findById(req.params.id);
        if(!bootcamp) {
            return next(new ErrorResponse(`Bootcamp not found with id of ${req.params.id}`, 404));
        }
        // 先删除该训练营下的所有课程
        await Course.deleteMany({ bootcamp: req.params.id });
        // 再删除训练营本身
        await Bootcamp.findByIdAndDelete(req.params.id);
        res.status(200).json({ success: true, data: {} });
})

这种方式逻辑直观,但如果需要强一致性,建议用Mongoose事务包裹两个删除操作,避免出现删了课程但没删训练营的情况。

3. MongoDB原生级联删除(推荐生产环境)

如果你的MongoDB版本在4.4及以上,可以直接在Course模型的外键字段上配置onDelete: 'CASCADE',由数据库底层自动处理级联删除,这是最可靠的方案:

// Course模型定义
const CourseSchema = new mongoose.Schema({
    // 其他字段...
    bootcamp: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Bootcamp',
        required: true,
        index: true,
        // 配置级联删除
        onDelete: 'CASCADE'
    }
});

配置后,当Bootcamp文档被删除时,MongoDB会自动删除所有关联的Course文档,无需在应用层编写任何额外逻辑。


内容的提问来源于stack exchange,提问作者tukitooo1998

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 00:43:20