NodeJS Express+MongoDB应用500错误:post-detail页面加载失败求助
开发基于NodeJS Express和MongoDB的Web应用时,在文章详情页(post-detail)通过AJAX加载评论出现Failed to load resource: the server responded with a status of 500 (Internal Server Error)错误。移除评论显示逻辑后页面及评论提交表单可正常显示,添加错误日志后得到如下报错:
ReferenceError:
C:\Users\DELL\Desktop\node-com4muz-filedata-database\views\home\post-detail.ejs:30
28|
29|
30| <% if (!comments) { %>
31|
32| This post might have comments. You can load them if you want to view
33| them.comments is not defined
at eval ("C:\Users\DELL\Desktop\node-com4muz-filedata-database\views\home\post-detail.ejs":40:8)
at post-detail (C:\Users\DELL\Desktop\node-com4muz-filedata-database\node_modules\ejs\lib\ejs.js:703:17)
at tryHandleCache (C:\Users\DELL\Desktop\node-com4muz-filedata-database\node_modules\ejs\lib\ejs.js:274:36)
at View.exports.renderFile [as engine] (C:\Users\DELL\Desktop\node-com4muz-filedata-database\node_modules\ejs\lib\ejs.js:491:10)
at View.render (C:\Users\DELL\Desktop\node-com4muz-filedata-database\node_modules\express\lib\view.js:135:8)
at tryRender (C:\Users\DELL\Desktop\node-com4muz-filedata-database\node_modules\express\lib\application.js:657:10)
at Function.render (C:\Users\DELL\Desktop\node-com4muz-filedata-database\node_modules\express\lib\application.js:609:3)
at ServerResponse.render (C:\Users\DELL\Desktop\node-com4muz-filedata-database\node_modules\express\lib\response.js:1039:7)
at C:\Users\DELL\Desktop\node-com4muz-filedata-database\routes\home\posts.js:49:7
at processTicksAndRejections (node:internal/process/task_queues:96:5) { path:
'C:\Users\DELL\Desktop\node-com4muz-filedata-database\views\home\post-detail.ejs'
}
相关代码
app.js
const path = require('path'); const express = require('express'); const db = require('./data/database'); const adminRoutes = require('./routes/admin/blog'); const defaultRoutes = require('./routes/home/default'); const postsRoutes = require('./routes/home/posts'); const quotationsRoutes = require('./routes/home/quotations'); const contactsRoutes = require('./routes/home/contacts'); const app = express(); app.set('views', [ path.join(__dirname, 'views/home'), path.join(__dirname, 'views/admin') ]); app.set('view engine', 'ejs'); app.use(express.static('public')); app.use('/public/admin/images', express.static('public/admin/images')); app.use(express.urlencoded({ extended: true })); app.use('/', adminRoutes); app.use('/', defaultRoutes); app.use('/', postsRoutes); app.use('/', quotationsRoutes); app.use('/', contactsRoutes); app.use(function (req, res) { res.status(404).render('404'); }); app.use(function (error, req, res, next) { res.status(500).render('500'); }); db.connectToDatabase().then(function () { app.listen(3000); });
routes/home/posts.js
const express = require('express'); const mongodb = require('mongodb'); // const uuid = require('uuid'); const db = require('../../data/database'); const ObjectId = mongodb.ObjectId; const router = express.Router(); router.get('/blog', async function (req, res) { const posts = await db .getDb() .collection('posts') .find({}) .project({ title: 1, summary: 1, 'author.name': 1, imagePath: 1 }) .toArray(); res.render('posts', { posts: posts }); }); router.get('/blog/:id', async function (req, res, next) { let postId = req.params.id; try { postId = new ObjectId(postId); } catch (error) { return res.status(404).render('404'); // return next(error); } const post = await db .getDb() .collection('posts') .findOne({ _id: postId }, { summary: 0 }); if (!post) { return res.status(404).render('404'); } post.humanReadableDate = post.date.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); post.date = post.date.toISOString(); res.render('post-detail', { post: post }); }); router.get('/blog/:id/comments', async function (req, res) { const postId = new ObjectId(req.params.id); const comments = await db .getDb() .collection('comments') .find({ postId: postId }) .toArray(); res.json(comments); }); router.post('/blog/:id/comments', async function (req, res) { const postId = new ObjectId(req.params.id); const newComment = { postId: postId, title: req.body.title, text: req.body.text }; await db.getDb().collection('comments').insertOne(newComment); res.redirect('/blog/' + req.params.id); }); module.exports = router;
views/home/post-detail.ejs
<!DOCTYPE html> <html lang="en"> <head> <%- include('../admin/includes/head', { title: 'Post title' }) %> <link rel="stylesheet" href="/admin/styles/posts.css" /> <link rel="stylesheet" href="/admin/styles/forms.css" /> <script src="/home/scripts/comments.js" defer></script> </head> <body> <%- include('../admin/includes/header') %> <main id="post-detail"> <article class="post-item"> <img src="/<%= post.imagePath %>" alt="<%= post.title %>" /> <h1><%= post.title %></h1> <section id="post-meta"> <address> <a href="mailto:<%= post.author.email %>" ><%= post.author.name %></a > </address> | <time datetime="<%= post.date %>"><%= post.humanReadableDate %></time> </section> <hr /> <section> <p id="body"><%= post.body %></p> </section> </article> <section id="comments"> <% if (!comments) { %> <p> This post might have comments. You can load them if you want to view them. </p> <button id="load-comments-btn" class="btn btn-alt" data-postid="<%= post._id %>" > Load Comments </button> <% } else if (comments.length === 0) { %> <p>No comments found.</p> <% } else { %> <ol> <% for (const comment of comments) { %> <li><%- include('includes/comment-item', { comment: comment }) %></li> <% } %> </ol> <% } %> </section> <section id="comments-form"> <h2>Leave a comment</h2> <form action="/posts/<%= post._id %>/comments" method="POST"> <div class="form-control"> <label for="title">Comment title</label> <input type="text" id="title" name="title" required /> </div> <div class="form-control"> <label for="text">Your comment</label> <textarea name="text" id="text" rows="3" required></textarea> </div> <button class="btn">Save Comment</button> </form> </section> </main> </body> </html>
views/home/includes/comments/comment-item.ejs
<article class="comment-item"> <h2><%= comment.title %></h2> <p><%= comment.text %></p> </article>
public/home/scripts/comments.js
const loadCommentsBtnElement = document.getElementById('load-comments-btn'); async function fetchCommentsForPost() { const postId = loadCommentsBtnElement.dataset.postid; const response = await fetch(`/blog/${postId}/comments`); const responseData = await response.json(); console.log(responseData); } loadCommentsBtnElement.addEventListener('click', fetchCommentsForPost);
解决方案
核心问题
EJS模板渲染post-detail.ejs时,尝试访问未定义的comments变量,直接抛出引用错误导致500状态码。根源是routes/home/posts.js的/blog/:id路由渲染模板时,仅传递了post对象,未初始化comments变量。
修复步骤
初始化模板变量
修改routes/home/posts.js中/blog/:id路由的渲染代码,添加comments变量初始化:res.render('post-detail', { post: post, comments: undefined // 或null,确保变量存在 });模板中的
if (!comments)判断即可正常执行,不会因变量未定义报错。AJAX加载后动态渲染评论
当前comments.js仅打印评论数据,未更新页面。修改fetchCommentsForPost函数,获取数据后动态生成DOM:async function fetchCommentsForPost() { const postId = loadCommentsBtnElement.dataset.postid; const response = await fetch(`/blog/${postId}/comments`); const comments = await response.json(); const commentsSection = document.getElementById('comments'); commentsSection.innerHTML = ''; if (comments.length === 0) { commentsSection.innerHTML = '<p>No comments found.</p>'; return; } const ol = document.createElement('ol'); comments.forEach(comment => { const li = document.createElement('li'); li.innerHTML = ` <article class="comment-item"> <h2>${comment.title}</h2> <p>${comment.text}</p> </article> `; ol.appendChild(li); }); commentsSection.appendChild(ol); }修复评论提交表单路径
post-detail.ejs中表单action属性写为/posts/<%= post._id %>/comments,但路由定义为/blog/:id/comments,需修改为:<form action="/blog/<%= post._id %>/comments" method="POST">避免提交评论时触发404错误。
内容的提问来源于stack exchange,提问作者Sofiane R

