尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Node.js+Express+MongoDB 博客系统实战:从环境搭建到上线排查

发布时间:2026/9/26 21:13:48

资讯中心
01
ARTICLE

Node.js+Express+MongoDB 博客系统实战:从环境搭建到上线排查

Node.js+Express+MongoDB 博客系统实战:从环境搭建到上线排查
简介一份基于 Node.js、Vue 与 MongoDB 的博客管理系统毕业设计项目包面向计算机相关专业学生及 Web 全栈入门者可同时服务于毕业设计、课程设计、项目实训与简历项目等场景。资源共 675 个文件解压后约 121.62MB核心以 js 源码、md 说明、json 配置为主并附带 gif 演示、png 图片、html 页面、PSD 设计稿及 SQL 初始化脚本完整覆盖前端展示、后端接口、数据存储、界面设计与运行文档等分层内容。系统功能齐全、界面美观并已经过测试能够快速部署启动。目前已有 163 人学习下载使用反馈相对稳定。读者可通过该项目深入理解 Express 中间件配置、MongoDB 数据建模、Vue 组件通信等关键知识点也能参考 PSD 设计稿和演示动图快速搭建演示环境从而更自信地完成成果展示与答辩说明。1. 一套能跑的博客系统Node.js Express MongoDB 到底在做什么很多人在学完 Node.js 语法之后都会卡在同一个问题上我会写接口会连数据库但把它们拼成一个完整、能上线、能维护的「系统」时总感觉少了一层东西。这套 NodejsExpressMongoDB 实现的博客管理系统恰好补的就是这层东西——它把用户注册登录、文章发布编辑、标签分类、列表分页这些博客最常见的功能用一套前后端打通的方式串了起来。你跟着做一遍收获的不是几十个零散接口而是一个可以继续往里面加功能评论、点赞、后台权限的骨架。这个技术栈之所以经典是因为三者分工足够清晰Node.js 负责运行 JavaScript 服务端代码Express 把请求路由、中间件、静态资源这些琐碎事管起来MongoDB 用文档模型承接博客这种「文章 作者 分类」的非固定结构数据。相比传统关系库你不需要在写第一行代码前就把表结构设计死这对快速迭代非常友好。但它也有自己的脾气比如默认的_id字段是 ObjectId 类型、连接串参数不能乱省、Windows 下装个 MongoDB 本身就可能让你先翻一次车——这些我下面都会展开讲。2. 环境准备不能省Node.js 与 MongoDB 的安装、环境变量与最小验证2.1 Node.js 安装版本选择、环境变量与 PowerShell 执行策略先把底座装好。Node.js 安装的原则只有一条去官网下 LTS 版本不要贪最新版。博客系统这种业务逻辑为主的项目用不上那些刚发布几周的新特性LTS 的稳定性是排第一位的。下载时选.msi安装包一路 Next 即可它会自动把node和npm写入系统 PATH。安装完成后第一件事不是急着写代码而是打开一个新的终端窗口旧窗口不会刷新环境变量依次验证两条命令node -v npm -v如果能看到类似v20.x.x和10.x.x的输出说明 Node 本体没问题。如果报「无法识别 node / npm」先检查环境变量里有没有C:\Program Files\nodejs\或D:\nodejs\取决于你的安装路径没有就手动加进去。这一步看着简单但很多人的项目跑不起来其实就是卡在 PATH 没配好。Windows 上还有一个高频坑执行npm命令时报无法加载文件 ...npm.ps1因为在此系统上禁止运行脚本。这不是 Node 的问题而是 PowerShell 默认的执行策略不允许跑.ps1脚本。解决方法是Set-ExecutionPolicy -Scope CurrentUser RemoteSigned执行后选Y确认再重开终端npm命令就正常了。注意只对CurrentUser设置不用动系统级策略这样最安全。2.2 MongoDB 安装服务启动方式与「装完连不上」的分水岭MongoDB 的安装比 Node.js 更容易让人翻车因为它有两种运行方式而你选哪一种直接决定了后续能不能连上。官方提供的.msi安装包在安装时默认会注册成 Windows 系统服务也就是开机自动后台运行。如果这一步没勾选或者安装时服务注册失败这很常见和杀毒软件、权限都有关系你就得手动用命令行启动。我的建议是不要依赖服务注册直接用命令行启动 MongoDB日志和报错都看得见。先建好数据目录和日志目录然后用mongod指定路径启动mkdir D:\mongodb\data\db mkdir D:\mongodb\logs mongod --dbpath D:\mongodb\data\db --logpath D:\mongodb\logs\mongod.log --logappend --port 27017这条命令的关键参数有三个--dbpath指向数据文件目录--port指定监听端口默认 27017这里显式写出是为了后面排查方便--logappend保证重启服务时日志不被覆盖。启动后看到Waiting for connections字样说明数据库已经正常监听。注意这个终端窗口要一直开着别关。每次都这样手动敲肯定不方便你可以把它存成一个start-mongodb.bat文件双击即启动。2.3 最小闭环验证从 node 到 mongodb 都通了的信号前面两步都做完你得用最短路径证明「Node 能连上 Mongo」而不是直接开写代码、最后把问题混在一起排错。打开终端进入项目目录用 Node 自带的mongodb驱动不用额外安装跑一次连接测试// test-connect.js const { MongoClient } require(mongodb); async function main() { // 27017 是 mongod 默认监听端口test 是数据库名 const client new MongoClient(mongodb://localhost:27017/test); await client.connect(); console.log([OK] MongoDB connected); const db client.db(test); // ping 命令用来确认数据库服务真正响应而不只是 TCP 通 await db.command({ ping: 1 }); console.log([OK] database ping success); await client.close(); } main().catch((err) { console.error([FAILED], err.message); process.exit(1); });运行node test-connect.js如果输出两行[OK]说明 Node.js、MongoDB 服务、端口、权限整条链路都是通的。注意这里先跑一次「空白验证」非常重要。很多人的博客系统写完跑不通最后定位到的问题是 MongoDB 根本就没启动——可代码层面翻了半天。做技术排查时永远先把无关变量砍到最少再谈业务逻辑。3. 用 Express 搭博客骨架入口文件、路由分层与 Mongoose 连接3.1 为什么直接上手写路由而不先谈脚手架博客管理系统的本质是一组「资源 操作」文章是资源增删改查是操作用户是资源注册登录是操作。Express 在这里扮演的是路由分发器的角色它把 HTTP 请求按 URL 和方法映射到对应的处理函数。很多人喜欢一上来就贴一个几十行的路由器文件但那样做你学到的是「抄代码」不是「搭系统」。常见做法是先用 Express 官方脚手架express-generator生成一个标准目录再在这个骨架上改。原因很简单它替你提前定好了routes/、views/、public/、app.js的职责边界而这些边界是一个博客管理系统后续能长期维护的根基。用脚手架生成项目npx express-generator --viewejs blog-system cd blog-system npm install这里用了npx是为了不全局安装脚手架--viewejs选择 EJS 模板引擎理由是它支持% %语法直接在 HTML 中渲染动态数据比纯手写字符串拼接要直观得多也比 React/Vue 这类前后端分离方案少一层跨域和构建配置——对博客这种以服务端渲染为主的系统EJS 的维护成本最低。执行完npm install后目录里会生成app.js、routes/、views/、public/这就是我们要的骨架。启动一次看看效果npm start浏览器访问http://localhost:3000看到 Express 的欢迎页说明骨架通了。到这里你还没碰业务代码但「路由结构长什么样、静态资源往哪里放、模板文件在哪里」这个问题已经有了答案。3.2 app.js 里那些必须搞懂的挂载点脚手架生成的app.js不用大改但其中有几个配置直接影响后面写业务得挨个说清楚。先看这份简化的核心代码它基本就是脚手架自动生成的样子只是去掉了注释// app.js const express require(express); const path require(path); const cookieParser require(cookie-parser); const logger require(morgan); const indexRouter require(./routes/index); const usersRouter require(./routes/users); const app express(); // 视图引擎配置模板放在 views 目录后缀是 .ejs app.set(views, path.join(__dirname, views)); app.set(view engine, ejs); // 以下三个中间件按顺序执行顺序就是请求处理顺序 app.use(logger(dev)); // 打印请求日志 app.use(express.json()); // 解析 JSON 请求体 app.use(express.urlencoded({ extended: false })); // 解析表单请求体 app.use(cookieParser()); // 解析 Cookie登录态会用到 app.use(express.static(path.join(__dirname, public))); // 静态资源映射 // 路由挂载/users 开头走 usersRouter其余走 indexRouter app.use(/, indexRouter); app.use(/users, usersRouter); module.exports app;这里最值得琢磨的是两个点。一是express.json()和express.urlencoded()缺一不可前端用fetch发 JSON 时走前者用 HTML 表单提交时走后者。博客系统两种都有API 调用和后台表单所以都要挂载。二是express.static()把public/目录暴露给浏览器你在public/css里放的样式文件通过/css/xxx.css直接访问不需要额外写路由。3.3 Mongoose 连接参数补齐与「连接失败优先查什么」骨架没问题了接下来是让 Express 和 MongoDB 建立正式连接。这里引入mongoose它是 MongoDB 官方的 ODM 库作用是把数据库的文档映射成 JavaScript 对象模型并提供 Schema 校验能力。安装并建立连接npm install mongoose dotenv// config/db.js const mongoose require(mongoose); require(dotenv).config(); const connectDB async () { try { // 连接串格式mongodb://主机:端口/数据库名 await mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true, serverSelectionTimeoutMS: 5000, // 5秒内连不上直接报错而不是卡住 }); console.log([OK] MongoDB connected via mongoose); } catch (err) { console.error([FAILED] MongoDB connection error:, err.message); process.exit(1); // 数据库连不上时直接退出进程避免带病运行 } }; module.exports connectDB;再把连接串放进.env文件不放代码里这是从第一天就该养成的习惯MONGODB_URImongodb://localhost:27017/blog-system PORT3000process.env.MONGODB_URI从.env文件读取dotenv负责把它注入到进程环境变量。注意代码里的serverSelectionTimeoutMS: 5000它表示如果 5 秒内没有找到可用的 MongoDB 服务端连接直接失败而不是无限等待。这个参数在排查「为什么连不上」时特别有用——你会在 5 秒后立刻拿到错误信息而不是让程序假死在那里。连接建立后下一步就是在app.js里调用// app.js 顶部 const connectDB require(./config/db); connectDB();连 MongoDB 失败时的排查顺序我建议固定为第一步看mongod那个终端窗口有没有「Waiting for connections」没有就说明数据库没启动第二步看端口是否被占用执行netstat -ano | findstr :27017第三步看连接串里的数据库名是否拼错。按这个顺序来90% 的连接问题都能定位。剩下的 10%才需要往网络、权限那个方向想。4. 数据模型与核心接口用户注册、登录鉴权与文章 CRUD4.1 用户模型与密码哈希为什么不能用明文博客系统最核心的数据对象有两个用户和文章。先说用户模型。用户的信息必须包含username、password、email这些字段但password绝对不能明文存储。原因不需要多讲——数据库泄露一次所有用户的口令就全完了。所以这里在 Schema 层就把密码哈希掉用bcryptjs纯 JavaScript 实现不需要编译原生模块Windows 下安装省心// models/User.js const mongoose require(mongoose); const bcrypt require(bcryptjs); const userSchema new mongoose.Schema({ username: { type: String, required: true, unique: true, trim: true }, email: { type: String, required: true, unique: true, lowercase: true }, password: { type: String, required: true, minlength: 6 }, createdAt: { type: Date, default: Date.now }, }); // 在 save 之前自动执行把明文密码替换成哈希值 userSchema.pre(save, async function (next) { // 只有密码字段被修改时才重新哈希避免重复处理 if (!this.isModified(password)) return next(); const salt await bcrypt.genSalt(10); // 10 轮盐安全性和性能的折中点 this.password await bcrypt.hash(this.password, salt); next(); }); // 实例方法用于登录时校验用户输入的密码 userSchema.methods.comparePassword function (plainPassword) { return bcrypt.compare(plainPassword, this.password); }; module.exports mongoose.model(User, userSchema);这里要解释两个设计细节。pre(save)中间件保证了「只要调用user.save()密码一定是哈希过的」你不用在业务代码里到处记得做哈希。isModified(password)判断是为了防止更新用户名这种操作时把密码重新哈希一遍——那样会导致登录失效。bcrypt.genSalt(10)的 10 是计算代价因子数字越大哈希越慢、越难被暴力破解但用户登录时也会越慢10 是社区常见的平衡值。4.2 文章模型作者关联、状态与基础字段文章模型是博客的另一个核心。设计时最容易犯的错是把作者的名字直接存在文章表里比如author: 张三。但用户改个昵称历史文章的作者名就全部对不上了。正确的做法是存ObjectId引用查询时用populate把作者信息带出来——这是 MongoDB 文档模型中最有特色的操作也是这个技术栈和关系数据库最大的差异点。// models/Post.js const mongoose require(mongoose); const postSchema new mongoose.Schema({ title: { type: String, required: true, trim: true, maxlength: 120 }, content: { type: String, required: true }, // 存 ObjectId做数据关联ref 指明关联哪个模型 author: { type: mongoose.Schema.Types.ObjectId, ref: User, required: true }, category: { type: String, default: 未分类 }, tags: { type: [String], default: [] }, // 数组类型博客标签多对多直接用数组表达 status: { type: String, enum: [draft, published], default: published }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now }, }); module.exports mongoose.model(Post, postSchema);注意author字段的类型是ObjectId配合ref: User。查询文章时要拿到作者名在业务代码里这样写const posts await Post.find({ status: published }) .populate(author, username) // 只带出 username 字段不要整条用户记录 .sort({ createdAt: -1 }) .limit(10);populate(author, username)的含义是把author字段引用的 User 文档填充进来第二个参数限制只取username这一个字段。排序用-1表示倒序limit(10)是最简单的分页控制。status字段用enum限定只有draft和published两个值这比随便存字符串要严谨得多后台管理员可以控制文章是否对外可见。4.3 注册登录接口与 Session 鉴权三个必调参数有了模型就可以写接口了。博客管理系统的登录态管理我选择用express-session而不是 JWT原因有两个一是博客系统是单体应用所有请求都打在同一个服务上Session 天然合适二是 Session 存储在服务端管理员可以随时踢人下线JWT 做不到这种「立即失效」的控制。安装并配置npm install express-session// app.js 中挂载 session 中间件 const session require(express-session); app.use(session({ secret: process.env.SESSION_SECRET || change-me-in-production, resave: false, // 不强制保存未修改的 session saveUninitialized: false, // 没登录的用户不创建 session 记录 cookie: { httpOnly: true, // 禁止脚本读取 cookie防 XSS 窃取登录态 maxAge: 1000 * 60 * 60 * 24, // 24 小时后过期 sameSite: lax, // 基本防 CSRF } }));这三个参数值得逐个看。resave: false是告诉 Express 不要在每次请求后都重新保存 session省掉大量无效的数据库/内存写入saveUninitialized: false表示只有用户真正登录产生了 session 数据后才创建 session不然每个匿名访客都会在服务端留一份空记录大量刷访客时内存会爆httpOnly: true让 JavaScript 无法通过document.cookie读取到 session ID这是防 XSS 窃取登录态的第一道闸门。再看注册和登录两个接口的完整逻辑。注册很简单创建一个 User 文档并保存。登录则是「查用户、比密码、记 session」三步// routes/auth.js const express require(express); const User require(../models/User); const router express.Router(); // POST /auth/register router.post(/register, async (req, res) { const { username, email, password } req.body; try { // 唯一索引已经保证 username 和 email 不重复 // 这里如果出错会抛代码 11000 的 duplicate key 错误 const user new User({ username, email, password }); await user.save(); res.redirect(/login); } catch (err) { res.status(400).render(error, { message: err.code 11000 ? 用户名或邮箱已存在 : err.message }); } }); // POST /auth/login router.post(/login, async (req, res) { const { username, password } req.body; // findOne 按用户名查密码比对交给模型里的 comparePassword const user await User.findOne({ username }); if (!user || !(await user.comparePassword(password))) { return res.status(401).render(login, { error: 用户名或密码错误 }); } // 登录成功把用户 id 写进 session后续请求靠它识别身份 req.session.userId user._id; req.session.username user.username; res.redirect(/dashboard); });注册接口的错误处理有个小玄学user.save()抛出的错误里err.code 11000是 MongoDB 唯一索引冲突的标准代码你要把它翻译成「用户名或邮箱已存在」这样的用户可读信息而不是直接抛一个英文报错页面。登录接口里req.session一旦被写入浏览器就会在响应头里收到Set-Cookie之后的每次请求会自动带上sessionId服务端识别出req.session.userId就等于识别出了当前用户是谁。4.4 文章管理接口登录状态拦截、创建与列表文章接口是整个系统的核心需要先加上一个「只有登录用户才能发文章」的拦截逻辑把这个公共逻辑做成中间件// middlewares/auth.js // 有 userId 说明是登录用户没有就跳回登录页 function requireLogin(req, res, next) { if (!req.session.userId) { return res.redirect(/login); } next(); }挂载方法是在路由文件里引用这个中间件// routes/posts.js const express require(express); const Post require(../models/Post); const requireLogin require(../middlewares/auth); const router express.Router(); // 全部文章列表游客也能看所以这里不挂中间件 router.get(/, async (req, res) { // populate 把作者姓名带出来列表页要显示「作者xx」 const posts await Post.find({ status: published }) .populate(author, username) .sort({ createdAt: -1 }); res.render(posts/index, { posts, user: req.session.userId }); }); // 创建文章必须登录POST 请求 router.post(/create, requireLogin, async (req, res) { const { title, content, category, tags } req.body; // author 从 session 里取而不是相信前端传的用户 id防止越权 try { await Post.create({ title, content, category, tags: tags ? tags.split(,).map((t) t.trim()) : [], author: req.session.userId, }); res.redirect(/dashboard); } catch (err) { res.status(400).render(error, { message: err.message }); } });注意author字段是从req.session.userId取的而不是从请求体里取。这是权限设计的关键原则身份凭证只信服务端 session绝不信前端传的参数。不然任何人都可以伪造一个author别人_id的请求给任意用户安上文章。tags字段处理则是把前端传的「Node.js, MongoDB」这样的逗号字符串拆成数组存入模型这是博客标签管理常用的简单方案。删除和更新接口的写法逻辑完全一样区别只在findByIdAndUpdate和findByIdAndDelete这两个 Mongoose 方法更新时记得同时刷新updatedAt字段router.post(/delete/:id, requireLogin, async (req, res) { await Post.findByIdAndDelete(req.params.id); res.redirect(/dashboard); });这里有个值得提醒的细节findByIdAndDelete其实没有校验「这篇文章是不是当前登录用户写的」严格来说当前用户随便编一个不存在的文章 id 也能触发一次删除操作只是删不到东西而如果系统要让用户只能删自己的文章就要在删除前先findOne({ _id: req.params.id, author: req.session.userId })查到就删查不到就拒绝。这是博客系统越权漏洞最常见的来源也是面试时爱问的点。5. 高频踩坑与排查npm 脚本、MongoDB 连接、ObjectId 与中文乱码5.1 npm.ps1 执行策略报错Windows 特有的拦路虎这是 Windows 环境新手上路遇到概率最高的一个报错在 VS Code 或 PowerShell 里敲npm开头任何命令半天不出结果最后抛出来无法加载文件 ...npm.ps1因为在此系统上禁止运行脚本。整行的关键信息是禁止运行脚本以及npm.ps1。原因是 PowerShell 出于安全考虑默认禁止执行磁盘上的.ps1脚本文件而 npm 给 PowerShell 提供的其实是npm.ps1这个包装脚本正好被拦住了。解决分两步走。第一步以管理员身份打开 PowerShell执行Set-ExecutionPolicy RemoteSigned然后确认这会允许本机脚本运行同时仍然拦截从网上下载的未签名脚本。第二步关掉所有终端窗口重开让策略生效。如果公司电脑有组策略限制执行报错没权限那就在普通终端里用npm.cmd代替npm比如npm.cmd install express效果一样。这个踩坑记录写在前面是因为它不需要任何业务代码知识纯环境问题但卡住的人最多。5.2 MongoDB 服务没启动或端口被占用连接失败的三个排查方向博客系统写完一翻车最常见的就是MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017。这条错误的字面含义是「连 27017 端口被拒绝」但真实原因往往有三个层面。第一层mongod进程根本没起来或者那个终端窗口被你关了。可视化验证办法是用浏览器访问http://localhost:27017能显示It looks like you are trying to access MongoDB over HTTP就是服务在跑。第二层端口被占Windows 上执行netstat -ano | findstr :27017如果这命令返回了一行但进程不是mongod.exe说明你的 27017 端口被别的程序抢了最常见的是之前一次异常退出留下的僵尸 mongod 进程。解决是把那个 PID 对应的进程杀掉或者直接把mongod的端口改成27018然后同步改.env里的连接串。第三层数据库服务起来了但连接串的数据库名写错了。Mongoose 连接串的第三段是数据库名mongodb://localhost:27017/blog-system会创建一个名为blog-system的数据库不存在也不报错但查询全空——这种静默错误比报错更阴险排查时一定要先用 MongoDB Compass 打开确认数据到底写进了哪个库。5.3_id是 ObjectId 不是字符串路由参数导致查不到数据第二个高频坑出现在写文章详情页时路由定义是/posts/:id你用Post.findById(req.params.id)去查结果查询结果始终是null而用 Compass 看数据库里明明有这个文档。问题出在req.params.id的类型是字符串而 MongoDB 的_id字段是ObjectId类型。字符串6458abc...和 ObjectId6458abc...是两种不同的值。好消息是 Mongoose 的findById会自动把传入的字符串转换成 ObjectId所以正常情况不会出问题。但一旦你的代码换成find({ _id: req.params.id })这种写法就走到另一条逻辑里去了——find不走自动转换拿着字符串去比对 ObjectId结果必然是空。解决有两个方案显式转换或用findByIdconst mongoose require(mongoose); const { ObjectId } mongoose.Types; // 方案 AfindById 会自动转换字符串 const post await Post.findById(req.params.id); // 方案 B先转换再查询 const id ObjectId.isValid(req.params.id) ? new ObjectId(req.params.id) : null; const post id ? await Post.findOne({ _id: id }) : null;方案 B 里先ObjectId.isValid再做转换是因为如果传入的 id 格式不对比如abc、undefinednew ObjectId()会抛异常导致整个接口 500而isValid可以把这种脏请求提前拦住。顺带说一个延伸坑ObjectId.isValid只检查格式不检查文档是否真的存在所以查不到时的空指针也要在模板渲染前处理。5.4 中文乱码模板渲染没问题问题出在请求体解析写完表单发布一篇中文标题的文章存进数据库是好的但网页上显示乱码或接口返回的中文变成䏿–‡。这里有两种完全不同的情况。第一种是网页显示乱码但数据库数据正常这多半是响应头里缺charset声明。用 Express 时确保响应设置了Content-Type: text/html; charsetutf-8EJS 模板文件里meta charsetUTF-8也不要省。第二种是数据库里直接存的乱码这就跟连接串有关了——MongoDB 连接串里如果目标库是通过 URL 参数指定的需要确认mongodb://localhost:27017/blog-system这串里的数据库名是英文不要出现直接从网页复制导致的全角字符。按经验来说本地开发环境这两种都排查完中文乱码的问题基本都能解决。5.5 Session 神秘失效重启服务就掉登录态最后一个坑可能出现在你把博客系统从前台切换到后台管理的路上明明登录了关掉浏览器重开就掉线或者代码改完用 nodemon 重启一下服务再刷新页面就跳回登录页了。前者要检查 session 的maxAge设置是不是设得太短后者要明白express-session默认把 session 数据存在内存里服务一重启内存清空所有登录态归零。生产环境不能依赖内存存 session否则重启一次全站用户都要重新登录。常见做法是把 session 存进 MongoDB用connect-mongo这个库npm install connect-mongoconst MongoStore require(connect-mongo); app.use(session({ secret: process.env.SESSION_SECRET || change-me-in-production, resave: false, saveUninitialized: false, store: MongoStore.create({ mongoUrl: process.env.MONGODB_URI, ttl: 60 * 60 * 24 // session 有效期 24 小时 }), }));store参数把 session 持久化到 MongoDBttl是过期时间单位秒。这样即使重启应用cookie 里携带的 session ID 依然能在数据库里找到对应的 session 记录登录态不会丢。这个改动工作量很小但对「系统可用性」的提升是质的——做了它你才敢把项目给别人演示。6. 交付前必做种子数据脚本、功能验证清单与三个长期习惯文章已经能发布了但要「交付」给别人看还差最后一公里——没有数据的空博客是没法演示的而靠人工一篇篇发布太浪费时间。我的习惯是写一个种子数据脚本一次性生成一个演示用户加几篇文章验收时打开网站就能看到效果// seed.js const mongoose require(mongoose); const User require(./models/User); const Post require(./models/Post); require(dotenv).config(); async function seed() { await mongoose.connect(process.env.MONGODB_URI); // 先清空旧数据让脚本可以反复执行 await Promise.all([User.deleteMany(), Post.deleteMany()]); const user await User.create({ username: admin, email: adminexample.com, password: admin123, }); await Post.create([ { title: 你好MongoDB, content: 第一篇测试文章, author: user._id, category: 日记, tags: [MongoDB] }, { title: Express 路由入门, content: 第二篇测试文章, author: user._id, category: 教程, tags: [Express, Node.js] }, ]); console.log([OK] seed data created. admin / admin123); mongoose.disconnect(); } seed().catch((err) { console.error(err); process.exit(1); });执行node seed.js然后按一张验证清单走一遍注册新用户、登录、发布文章、列表页显示、退出登录后再访问后台被拦截回登录页、重启服务后登录态还在。这六步全过这套 NodejsExpressMongoDB 的博客管理系统就算站住了。最后我多说三个长期习惯环境变量永远不写死在代码里数据库启动脚本存成.bat或.sh文件放进项目根目录mongodump定期备份blog-system库。这些东西都不难但能让你在一年后回来看这个项目时不用对着报错重新翻一遍今天的踩坑记录。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。