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

Handlebars中{{restaurant.rest_id}}未渲染致PUT请求报错问题排查

问题:餐厅管理系统编辑功能提交报错Cannot PUT /admin/restaurants/

开发餐厅管理系统时,测试编辑功能点击提交后出现错误提示Cannot PUT /admin/restaurants/,发现URL中未渲染出{{restaurant.rest_id}}。检查所有路径和代码后仍未找到问题,其他功能(如创建功能)可正常运行。改用{{restaurant.id}}并修改对应代码后,点击提交按钮可正常工作。想了解该问题的原因,是否与Handlebars的语法规则有关?


问题原因分析

这个问题和Handlebars语法无关,核心原因是传入模板的restaurant对象中根本不存在rest_id属性,只有id属性。

从你提供的console.log(restaurant)输出可以明确看到:

{
  id: 158,
  name: 'Lonnie Rath',
  tel: '1-380-799-1151 x9618',
  address: '1901 Dandre Light',
  openingHours: '8:00',
  description: 'Sed veniam dolore non. In maxime eius dicta necessitatibus nobis repellat autem fuga nemo. Reprehenderit repellendus ad non ratione quisquam esse occaecati. Sed minima similique neque aspernatur in perferendis at eum dolore. Quia sit vel autem sunt.',
  image: 'https://loremflickr.com/320/240/restaurant,food/?random=86.22619138396297',
  createdAt: 2023-01-31T04:14:46.000Z,
  updatedAt: 2023-01-31T04:14:46.000Z
}

对象的主键字段是id,而非你在模板中使用的rest_id。Handlebars遇到不存在的属性时,会渲染为空字符串,最终导致表单提交的URL变成/admin/restaurants/?_method=PUT,后端找不到匹配的PUT路由(你定义的PUT路由是/admin/restaurants/:rest_id),因此抛出Cannot PUT /admin/restaurants/的错误。

额外的字段名一致性问题

你在路由中定义的参数是:rest_id(比如router.put('/restaurants/:rest_id', adminController.putRestaurant)),但从数据库查询返回的是id,这里存在命名不一致:

  • 如果你模板使用{{restaurant.id}},路由中的:rest_id依然可以正常工作,因为Express会把URL中对应位置的值解析到req.params.rest_id;
  • 更稳妥的方式是统一命名,比如把路由参数改成:id,同时在控制器中用req.params.id获取,减少后续维护的混淆。

为什么创建功能不受影响?

创建功能使用POST请求到/admin/restaurants,不需要传递ID参数,因此不会受到字段名错误的影响,所以可以正常运行。


总结

  1. 模板渲染失败是因为restaurant对象没有rest_id属性,Handlebars对不存在的属性渲染为空,导致URL不符合后端路由规则;
  2. 解决方案是统一使用数据库返回的实际字段名id,同时确保路由参数、模板、控制器中的引用逻辑一致;
  3. 该问题与Handlebars语法规则无关,纯粹是字段名不匹配导致的。

相关代码

creat-restaurant.hbs

<form action="/admin/restaurants" method="POST">
  <div class="mb-4 text-center">
    <h1 class="h3 font-weight-normal mb-3">Create Restaurant</h1>
  </div>

  {{> admin-restaurant-form}}
</form>

edit-reataurant.hbs(原错误版本)

<form action="/admin/restaurants/{{restaurant.rest_id}}?_method=PUT" method="POST">
  <div class="mb-4 text-center">
    <h1 class="h3 font-weight-normal mb-3">Edit Restaurant</h1>
  </div>
  {{> admin-restaurant-form}}
</form>

admin-restaurant-form.hbs

<div class="form-row mb-3">
  <label class="form-label" for="name">Name</label>
  <input class="form-control" type="text" name="name" id="name" placeholder="Enter name" value="{{restaurant.name}}"
    required>
</div>
<div class="form-row mb-3">
  <label class="form-label" for="tel">Tel</label>
  <input class="form-control" type="tel" name="tel" id="tel" placeholder="Enter tel" value="{{restaurant.tel}}">
</div>
<div class="form-row mb-3">
  <label class="form-label" for="address">Address</label>
  <input class="form-control" type="text" name="address" id="address" placeholder="Enter address"
    value="{{restaurant.address}}">
</div>
<div class="form-row mb-3">
  <label class="form-label" for="openingHours">Opening Hours</label>
  {{!-- note type="time" is not supported by Safari--}}
  <input class="form-control" type="time" name="openingHours" id="openingHours" placeholder="Enter opening hours"
    value="{{restaurant.openingHours}}">
</div>
<div class="form-row mb-3">
  <label class="form-label" for="description">Description</label>
  <textarea class="form-control" id="description" name="description" rows="3">{{restaurant.description}}</textarea>
</div>
<button type="submit" class="btn btn-primary">Submit</button>

routes/modules/admin.js

const express = require('express');
const router = express.Router();
const adminController = require('../../controllers/admin-controller');

// Section all routes

router.get('/restaurants/create', adminController.createRestaurant);

router.get('/restaurants/:rest_id/edit', adminController.editRestaurant);

router.get('/restaurants/:rest_id', adminController.getRestaurant);

router.put('/restaurants/:rest_id', adminController.putRestaurant);

router.get('/restaurants', adminController.getRestaurants);

router.post('/restaurants', adminController.postRestaurant);

router.get('/', (req, res) => {
  res.redirect('/admin/restaurants');
});

module.exports = router;

app.js

const express = require('express');
const { engine } = require('express-handlebars'); // the syntax is already changed by newest version
const flash = require('connect-flash');
const methodOverride = require('method-override');
const session = require('express-session');
const passport = require('./config/passport');

const handlebarsHelpers = require('./helpers/handlebars-helpers'); 
const { getUser } = require('./helpers/auth-helpers');

const routes = require('./routes');

const app = express();
const port = process.env.PORT || 3000;
const SESSION_SECRET = 'secret';

app.engine('hbs', engine({ extname: '.hbs', helpers: handlebarsHelpers }));
app.set('view engine', 'hbs');

app.use(express.urlencoded({ extended: true }));

app.use(methodOverride('_method'));

app.use(
  session({ secret: SESSION_SECRET, resave: false, saveUninitialized: false })
);

app.use(passport.initialize());
app.use(passport.session()); // active session function

app.use(flash());
app.use((req, res, next) => {
  res.locals.success_messages = req.flash('success_messages');
  res.locals.error_messages = req.flash('error_messages');
  res.locals.user = getUser(req);
  next();
});

app.use(routes);

app.listen(port, () => {
  console.info(`Example app listening on port ${port}!`);
});

module.exports = app;

controllers/admin-controller.js

const { Restaurant } = require('../models');
const { localFileHandler } = require('../helpers/file-helpers');

const adminController = {
  // the object
  getRestaurants: (req, res, next) => {
    Restaurant.findAll({ raw: true })
      .then(restaurants => {
        res.render('admin/restaurants', { restaurants });
      })
      .catch(err => next(err));
  },
  createRestaurant: (req, res) => {
    return res.render('admin/create-restaurant');
  },
  postRestaurant: (req, res, next) => {
    const { name, tel, address, openingHours, description } = req.body;
    if (!name) throw new Error('Restaurant name is required');

    const file = req.file;
 
    localFileHandler(file)
      .then(filePath => {
        return Restaurant.create({
          name,
          tel,
          address,
          openingHours,
          description,
          image: filePath || null
        });
      })
      .then(() => {
        req.flash('success_messages', 'Restaurant was created successfully');
        res.redirect('/admin/restaurants');
      })
      .catch(err => next(err));
  },
  getRestaurant: (req, res, next) => {
    Restaurant.findByPk(req.params.rest_id, { raw: true })
      .then(restaurant => {
        if (!restaurant) throw new Error("Restaurant didn't exist");

        res.render('admin/restaurant', { restaurant });
      })
      .catch(err => next(err));
  },
  editRestaurant: (req, res, next) => {
    Restaurant.findByPk(req.params.rest_id, { raw: true })
      .then(restaurant => {
        if (!restaurant) throw new Error("Restaurant didn't exist");

        res.render('admin/edit-restaurant', { restaurant });
      })
      .catch(err => next(err));
  },
  putRestaurant: (req, res, next) => {
    const { name, tel, address, openingHours, description } = req.body;
    if (!name) throw new Error('Restaurant name is required');

    const { file } = req;

    Promise.all([
      Restaurant.findByPk(req.params.rest_id),
      localFileHandler(file)
    ])
      .then(([restaurant, filePath]) => {
        if (!restaurant) throw new Error("Restaurant didn't exist");

        return restaurant.update({
          name,
          tel,
          address,
          openingHours,
          description,
          image: filePath || restaurant.image
        });
      })
      .then(() => {
        req.flash('success_messages', 'Restaurant was updated successfully');
        res.redirect('/admin/restaurants');
      })
      .catch(err => next(err));
  },
  deleteRestaurant: (req, res, next) => {
    return Restaurant.findByPk(req.params.rest_id)
      .then(restaurant => {
        if (!restaurant) throw new Error("Restaurant didn't exist !!");

        return restaurant.destroy();
      })
      .then(() => res.redirect('/admin/restaurants'))
      .catch(err => next(err));
  }
};

module.exports = adminController;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 06:50:28