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

待办清单项目传入变量后Handlebars模板不渲染且无报错如何排查?

问题现象

我正在开发一个待办清单项目,但是我的Handlebars模板无法渲染。我使用Passport实现谷歌身份认证,新增的待办内容可以正常存入MongoDB数据库。
根据hbs官方文档,如果if块的参数返回false、undefined、null、""、0或[],则该块不会渲染。我已经确认传入了对应的变量,但控制台没有输出任何报错。

我已经被这个问题困扰很久了,以下是我的代码及注释内容:


相关代码

路由代码

const express = require("express");
const router = express.Router();
const todosController = require("../controllers/todos");

router.get("/", todosController.getTodos);

router.post("/addTodo", todosController.addTodo);

module.exports = router;

控制器代码

const Todo = require("../models/todos");

module.exports = {
  getTodos: async (req, res) => {
    // console.log(req.user);
    // console.log(req.user.googleId);
    try {
      const todoItems = await Todo.find({ googleId: req.user.googleId }); // get all todos that match googleId
      const itemsLeft = await Todo.countDocuments({
        googleId: req.user.googleId,
        done: false,
      }); // getting variables from google auth here
      console.log(itemsLeft);
      
      res.render("todos.hbs", {
        todo: todoItems, // Storing variables here and passing them on into hbs.
        left: itemsLeft,
        user: req.user,
      });
    } catch (err) {
      console.error(err);
    }
  },
  
  addTodo: async (req, res) => {
    // console.log(req.body.todoItem);
    try {
      await Todo.create({
        todo: req.body.todoItem,
        done: false,
        googleId: req.user.googleId,
      });
      console.log("To do has been added!");
      res.redirect("/");
    } catch (err) {
      console.error(err);
    }
  },
};

Handlebars模板代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>To Do</title>
  </head>
  <body>
    <div class="container">
      <header class="flexContainer">
        <h1 class="title main-font center">Welcome {{user.displayName}}</h1> 
        {{!-- not rendering. Maybe I need to use lean()? --}}
      </header>

      <form class="center" action="/todos/addTodo" method="POST">
        <input type="text" placeholder="Add a To Do" name="todoItem" />
        <button type="submit" class="submitButton">
          <i class="fa fa-plus-square"></i>
        </button>
      </form>

      <div class="to-do-list flexContainer">
        <ul class="task-list center">
          {{#each todo}}
            {{#if todo.done}} // 
            <span class="complete">{{todo.todo}}</span>
            {{else}}
            <h1>unknown</h1>
            <span class="incomplete">{{todo.todo}}</span>
            {{/if}}
            <span class="fa fa-trash delete-todo"></span>
          {{/each}}
          </li>
        </ul>
      </div>

      <h4 class="main-font center">Left to do: {{left}}</h4>
    </div>
    <script type="text/javascript" src="/js/main.js"></script>
  </body>
</html>

app.js配置代码

const path = require("path");
const express = require("express"); // handle CRUD api // <-- here
const exphbs = require("express-handlebars"); // <-- here
const mongoose = require("mongoose"); // talk to database
const dotenv = require("dotenv");
const morgan = require("morgan");
const passport = require("passport"); // handle all auth
const session = require("express-session"); // keep users logged in
const MongoStore = require("connect-mongo"); // pass session to database. Can get back in even if server restarts.
const connectDB = require("./config/db"); // how we connect to database

// Load config
// config.env is where all the global variables are stored.
dotenv.config({ path: "./config/config.env" });

// Passport config
require("./config/passport")(passport);

// This call connects to mongodb.
connectDB();

// Initialize app
const app = express();

// Body parser
// this allows us to look into data that was sent from forms
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

if (process.env.NODE_ENV === "development") {
  app.use(morgan("dev"));
}

// Handlebars <-- here
app.engine(
  ".hbs",
  exphbs({
    defaultLayout: "main",
    extname: ".hbs",
  })
);
app.set("view engine", ".hbs"); // <-- here

// Sessions
app.use(
  session({
    secret: "keyboard cat",
    resave: false,
    saveUninitialized: false,
    store: MongoStore.create({ mongoUrl: process.env.MONGO_URI }),
  })
);

// Passport middleware
app.use(passport.initialize());
app.use(passport.session());

// Static folder
app.use(express.static(path.join(__dirname, "public")));

const PORT = process.env.PORT || 3000;

// Routes
app.use("/", require("./routes/index"));
app.use("/auth", require("./routes/auth"));
app.use("/todos", require("./routes/todos"));

app.listen(
  PORT,
  console.log(`Server running in ${process.env.NODE_ENV} mode on PORT ${PORT}`)
);

项目目录结构

folder structure


修复方案

一共有三处问题需要修改:

  1. Mongoose查询返回的是带元数据的文档对象,不是普通JS对象,Handlebars无法直接读取属性。需要在查询时加lean()方法,将结果转为普通对象。修改控制器中的查询代码:
// 原代码
const todoItems = await Todo.find({ googleId: req.user.googleId });
// 修改后
const todoItems = await Todo.find({ googleId: req.user.googleId }).lean();

如果user.displayName无法渲染,也可以将req.user转为普通对象再传入模板:

res.render("todos.hbs", {
  todo: todoItems,
  left: itemsLeft,
  user: req.user.toJSON()
});
  1. Handlebars的each循环内,上下文默认指向当前遍历的子项,不需要再加外层的todo.前缀。同时模板中存在多余的</li>闭合标签,缺少对应的<li>开始标签,需要补全结构。修改模板的列表部分:
<div class="to-do-list flexContainer">
  <ul class="task-list center">
    {{#each todo}}
    <li>
      {{#if done}}
      <span class="complete">{{todo}}</span>
      {{else}}
      <h1>unknown</h1>
      <span class="incomplete">{{todo}}</span>
      {{/if}}
      <span class="fa fa-trash delete-todo"></span>
    </li>
    {{/each}}
  </ul>
</div>

修改完成后即可正常渲染模板内容。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 06:42:03