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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 05:57:17