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

Express.js提交按钮路由异常:PATCH请求无法正确跳转

待办事项项目PATCH请求问题排查

我正在做一个练习项目,用各类HTTP请求实现简单待办事项(to-do list)。除了PATCH请求,其他请求都正常:这个请求本该重定向到首页“/”,但实际跳转到“/posts/2”,出现“Cannot POST /posts/2”提示,而且数组里的元素也没修改。我想要它重定向到首页,展示包含已修改项的所有元素,但找不到错误。


后端代码(index.js)

import express from 'express';
import bodyParser from 'body-parser';

const app = express();
const port = 3000;

let todoList = [
    {
        id: 1,
        content: "Chase flogging pressgang aye overhaul dead men tell no tales mutiny lee Pieces of Eight draught.",    
    },
    {
        id: 2,
        content: "Barque trysail gunwalls yo-ho-ho scurvy gangway Blimey Davy Jones' Locker bounty weigh anchor.",
    },
];
let done = [];
var id = 2;
var data = {
    todo: todoList,
    done: done,
};

app.use(bodyParser.urlencoded({ extended : true }));
app.use(express.static('public'));
app.use(express.json());

app.get('/', (req, res) => {
    console.log('GET');
    res.render('index.ejs', data);
});

// 获取指定ID的待办项
app.get('/posts/:id', (req, res) => {
    if(todoList.length>0){
        let pId = parseInt(req.params.id);
        const post = todoList.find(i => i.id === pId);
        if(post){
            res.render('post.ejs', { post: post});
        }else {
            res.send('<h2>Post not found: <a href="/">返回首页</a></h2>');
        }
    } else {
        res.send('<h2>列表为空: <a href="/">返回首页</a><h2>');
    }
});

// 编辑页面路由
app.get('/edit/:id', (req, res) => {
    try {
        let pId = parseInt(req.params.id);
        const post = todoList.find(i => i.id === pId);
        if(!post) res.status(404).json({ message: "获取待办项失败。" });
        res.render("edit.ejs", {todo: post});
    } catch (error) {
        res.status(500).json({ message: "获取待办项失败。" });
    }
});

// 新增待办项
app.post('/posts', (req, res) => {
    if(req.body.post ){
        const newId = id += 1;
        const newTodo = {
            id: newId,
            content: req.body.post
        };
        todoList.push(newTodo);
    }
    res.redirect("/");
});

// 更新待办项(PATCH请求)
app.patch('/posts/:id', (req, res) => {
    console.log("PATCH");
    const pId = parseInt(req.params.id);
    console.log("pId: " + pId);
    const post = todoList.find((todo) => todo.id === pId);
    console.log("post.id: " + post.id);
    if(!post) res.status(404).json({message: '待办项不存在'});
    
    const replacementPost = {
        id: pId,
        content: req.body.content || post.content,
    };    
    const postIndex = todoList.findIndex((todo) => todo.id === pId);
    if (postIndex > -1){
        todoList[postIndex] = replacementPost;
        console.log("Updated: " + todoList[postIndex]);
        res.redirect("/");
    } else {
        res.send("未找到待办项内容");
    }
});

前端模板代码

index.ejs

<%- include('partials/header.ejs') %>
<% 
function excrept(text) { // 生成待办项短文本
    let shortTxt = text;
    let maxLength = 35;
    if (shortTxt.length > maxLength) {
        shortTxt = text.slice(0, maxLength) + "...";
    }

    return shortTxt;
}
%>
<h1>待办事项列表</h1>
<form action="/posts" method="POST">
    <textarea id="text" name="post" placeholder="输入你的待办事项" rows="5" autofocus required> </textarea>
    <input type="submit" value="添加待办"> 
</form>
<div class="todo">
    <% if(locals.todo && todo.length > 0) {%>
        <h2 id="todoList"> 待完成</h2>
        <% todo.forEach(element => { %>
            <div class="checklist-item">
                <input type="checkbox" id="<%= element.id %>" name="checkbox" class="checkbox" value="post-<%= element.id %>">
                <label for="<%= element.id %>"><a href="/posts/<%= element.id %>"><%= excrept(element.content) %></a></label> 
                <a class="edit" href="/edit/<%= element.id %>">编辑</a>
                <br>
            </div>
        <% }); %>
    <% } %>
</div>
<%- include('partials/footer.ejs') %>

edit.ejs

<%- include('partials/header.ejs') %>

<h1>修改待办项</h1>
<% if(locals.todo) { %>
<form id="editTodo" action="/posts/<%= todo.id %>" method="post">
    <textarea name="content" id="txtcontent" rows="5"><%= todo.content %></textarea>
    <button type="submit" class="modify">保存修改</button>
</form>
<% } %>
<%- include('partials/footer.ejs') %>

问题分析与修复

核心问题

  1. 请求方法不匹配:edit.ejs中的表单用的是method="post",但后端只定义了app.patch('/posts/:id')路由,没有对应POST路由,因此触发Cannot POST /posts/2错误。
  2. HTML表单限制:原生HTML的<form>标签仅支持GET和POST两种方法,直接写method="patch"不会生效。

修复方案

方案1:改用POST路由处理更新(最简单)

把后端的app.patch修改为app.post,让表单的POST请求能匹配到路由:

// 将原来的patch改为post
app.post('/posts/:id', (req, res) => {
    console.log("POST 更新");
    const pId = parseInt(req.params.id);
    const post = todoList.find((todo) => todo.id === pId);
    if(!post) res.status(404).json({message: '待办项不存在'});
    
    const replacementPost = {
        id: pId,
        content: req.body.content || post.content,
    };    
    const postIndex = todoList.findIndex((todo) => todo.id === pId);
    if (postIndex > -1){
        todoList[postIndex] = replacementPost;
        res.redirect("/");
    } else {
        res.send("未找到待办项内容");
    }
});

方案2:用隐藏字段模拟PATCH方法(REST风格)

如果想严格遵循REST规范保留PATCH路由,需要借助method-override中间件模拟PATCH请求:

  1. 安装依赖:
npm install method-override
  1. 在index.js中引入并配置中间件:
import methodOverride from 'method-override';
// 放在bodyParser之后配置
app.use(methodOverride('_method'));
  1. 修改edit.ejs的表单,添加隐藏字段:
<form id="editTodo" action="/posts/<%= todo.id %>" method="post">
    <input type="hidden" name="_method" value="PATCH">
    <textarea name="content" id="txtcontent" rows="5"><%= todo.content %></textarea>
    <button type="submit" class="modify">保存修改</button>
</form>

额外优化点

index.ejs中存在表单嵌套错误:form标签在forEach外部打开,却在内部闭合,会导致HTML结构异常,建议移除多余的form标签(当前checkbox的提交逻辑未实现,暂时不需要包裹)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 09:27:03