Express/MongoDB调用deleteOne删除数据项失效问题排查
问题根因
删不掉数据是多个问题叠加导致的:
- 前端DOM取值逻辑完全错误:你写的
this.parentNode.childNodes[1].innerText根本拿不到账单名。垃圾桶图标的父节点是class="modifyBill"的div,这个div里原本只有垃圾桶图标一个子元素,childNodes[1]取到的是空白文本节点或者undefined,传不出有效账单名。 - EJS模板存在语法错误:垃圾桶的
<span class='fa fa-trash'>标签没有写闭合标签</span>,浏览器渲染时会打乱DOM结构,进一步导致节点遍历失效。 - 查询条件不匹配:就算你能拿到页面上的账单名,你在EJS里把账单名做了首字母大写处理,页面显示的值和数据库里存的原始
billName大小写不一致,deleteOne的查询条件匹配不到对应文档,自然不会删除数据。 - 服务端没有做操作结果校验:不管
deleteOne有没有匹配到文档、有没有真的执行删除,你都直接返回删除成功的响应,根本无法感知实际执行结果,容易误导排查方向。 - 用
billName作为删除条件本身存在设计缺陷:如果出现重名账单,会直接误删第一条匹配的数据,无法精确定位要删除的条目。 - 大概率漏了Express的JSON解析中间件:如果没有在路由前配置
app.use(express.json()),request.body会是undefined,根本拿不到前端传的参数。
修复方案
1. 修复EJS模板
补全未闭合的标签,把MongoDB生成的唯一账单_id直接绑定在垃圾桶图标上,不再靠遍历DOM节点取值:
<!-- Looping through bills --> <% for (let i = 0; i < info.length; i++) {%> <% const billName = info[i].billName const capitalizedBillName = billName.charAt(0).toUpperCase() + billName.slice(1) %> <section class="billBox billCard w-50"> <div> <h2>Name of Bill</h2> <h3><%= capitalizedBillName %></h3> </div> <div> <h2>Amount Due</h2> <h3><%= `$` + info[i].billCost %></h3> </div> <div> <h2>Due Date</h2> <h3><%= info[i].billDue %></h3> </div> <div class="modifyBill"> <!-- 补全span闭合标签,将账单id存在data自定义属性上 --> <span class='fa fa-trash' data-bill-id="<%= info[i]._id %>"></span> </div> </section> <% } %>
2. 修复前端删除逻辑
直接从点击元素的data属性取账单ID,等接口返回成功后再刷新页面:
const deleteTriggers = document.querySelectorAll('.fa-trash') Array.from(deleteTriggers).forEach((element)=>{ element.addEventListener('click', deleteBill) }) async function deleteBill(){ // 直接读取元素上绑定的账单id,不受DOM结构变动影响 const targetBillId = this.dataset.billId try{ const response = await fetch('/deleteBill', { method: 'delete', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ billId: targetBillId }) }) const result = await response.json() console.log(result) if (result.success) { location.reload() } else { alert(`删除失败:${result.message}`) } }catch(err){ console.error('请求异常:', err) } }
3. 修复服务端接口
先配置JSON解析中间件,用_id作为精确查询条件,增加执行结果校验和异常捕获:
注意:如果使用原生MongoDB驱动,需要在文件头部引入ObjectId:
const { ObjectId } = require('mongodb');如果使用Mongoose,直接传入字符串id即可,不需要手动转换。
// 必须在所有路由定义之前配置该中间件,否则无法解析JSON格式的请求体 app.use(express.json()) app.delete('/deleteBill', async (request, response) => { try { const deleteResult = await db.collection('bills').deleteOne({ // 用唯一id做精确匹配,避免误删 _id: new ObjectId(request.body.billId) }) // 校验实际删除结果 if (deleteResult.deletedCount === 1) { return response.json({ success: true, message: '账单删除成功' }) } return response.json({ success: false, message: '未找到对应账单' }) } catch (error) { console.error('删除操作异常:', error) return response.status(500).json({ success: false, message: '服务端执行错误' }) } })
该场景标准最佳实践
- 所有删除、更新类的写操作,必须用数据库生成的唯一
_id作为查询条件,禁止用名称、金额这类可重复、可修改的业务字段当匹配条件,避免误操作。 - 前端不要靠多层DOM节点遍历拿业务数据,直接把需要的参数存在对应元素的
data-*自定义属性上,就算后续调整页面DOM结构,也不会导致取值逻辑失效。 - 服务端所有写操作必须校验返回的执行结果(比如
deleteOne返回的deletedCount、updateOne返回的modifiedCount),不要默认操作一定成功,根据实际执行结果返回对应响应。 - 前端发起异步请求后,必须等接口返回明确的成功状态,再执行页面刷新、路由跳转这类操作,避免请求未完成就被中断。
- 服务端路由必须统一加异常捕获,不要让错误直接抛出导致服务进程崩溃,也不要不管执行结果都返回相同的成功响应。
- 使用Express接收JSON请求体时,必须提前配置
express.json()中间件,否则request.body会是undefined,无法拿到前端传参。
内容的提问来源于stack exchange,提问作者aquile hollins
相关产品推荐
相关产品推荐

