Node.js 是基于 Chrome V8 引擎的 JavaScript 运行时环境,让 JavaScript 能够在服务器端运行;Express 是基于 Node.js 的 Web 应用框架,用于快速搭建服务器和接口。本讲将从零开始,依次介绍 Node.js 与 npm 的基础概念、Express 服务器的搭建、静态页面托管、MongoDB 数据库的连接,以及新闻系统四个接口的实现与测试,最终让 Day 1 编写的页面在 localhost:8888 上真正运行起来。
《企业技能实习》· 新闻系统全栈项目实训
实操纪律:从本讲起,桌面需同时保持——MongoDB 服务运行中(后台服务,无需窗口)、服务器窗口(node app.js)、浏览器,缺一不可。
前置讲义
- Day 2 · MongoDB,本讲假设
news库与newsList集合已创建并导入数据,schema-demo.js已就位。
资源下载
- 成品项目
digi(教师发放),本讲的app.js以其为参照
1. Node.js 与 npm 基础概念
1.1 Node.js 是什么
Node.js 让 JavaScript 脱离浏览器也能运行。至此,JavaScript 一门语言承担三份工作:
| 场景 | 运行环境 | 对应课程 |
|---|---|---|
| 浏览器里操作页面 | Chrome 的 JS 引擎 | Day 1 |
| 服务器上接收请求 | Node.js | Day 3(本讲) |
| 连接数据库操作数据 | Node.js 中的 Mongoose | Day 3(本讲) |
体验运行方式的变化——新建 hello.js,写入 console.log("hello node"),然后在 cmd 执行:
# 直接运行一个 js 文件(不再需要浏览器)
node hello.js
1.2 模块化与 require
一个 js 文件就是一个模块。require("包名") 将安装好的工具包引入自己的文件中使用。本课程后端代码的第一行永远是"领工具":
var express = require("express");
var bodyParser = require("body-parser");
var mongoose = require("mongoose");
1.3 npm 与 package.json
- npm(Node Package Manager) 是 Node.js 的包管理器,相当于 JS 世界的应用商店;
npm init -y:将当前文件夹登记为一个 Node 项目,生成项目"身份证"package.json;- 安装依赖后项目中会多出两个内容:
node_modules:包的本体(大量小文件夹,不需要查看,也不随项目拷贝);package-lock.json:依赖版本锁。
规范:项目拷贝给他人时不包含 node_modules,只拷贝代码;对方执行
npm i即可按package.json中记录的清单还原全部依赖。
1.4 本项目三个依赖包的分工
| 包 | 职责 | 一句话概括 |
|---|---|---|
| express | 搭建服务器、托管页面、编写接口 | 前端与数据库之间的桥梁 |
| mongoose | 连接并操作 MongoDB | 数据库的快捷遥控器 |
| body-parser | 解析前端请求携带的数据 | 翻译官(新增、修改新闻时使用) |
2. 搭建 Express 服务器
2.1 初始化项目
在 cmd 中进入项目目录(提示符必须为 C:\news>,目录错误会导致后续全部操作无效):
cd /d C:\news
# 将当前文件夹登记为 Node 项目,生成 package.json
npm init -y
# 安装项目所需的三个包
npm i express mongoose body-parser
安装完成后核对目录变化:package.json 的 dependencies 中出现三个版本号,node_modules 文件夹生成。
2.2 编写最小服务器
新建 app.js,写入以下代码:
var express = require("express"); // 引入 express
var myapp = express(); // 创建服务
// 将 src 下的所有页面托管到服务器,默认访问文件夹中的 index.html
myapp.use(express.static("src"));
myapp.listen(8888); // 监听 8888 端口
2.3 启动服务器并验证页面
# 启动服务器(小技巧:输入 node a 后按 Tab 键自动补全文件名)
node app.js
在浏览器中验证两个地址:
localhost:8888/tmt—— Day 1 的新闻首页正式运行在服务器上(按F12切换手机模拟 414×736,与成品效果图/index.png对照);localhost:8888/system—— 管理系统占位页。
注意区别:页面不再通过双击文件打开,而是从服务器获取——地址栏是
localhost,不再是本地磁盘路径。
2.4 修改代码的重启规则
| 修改对象 | 是否需要重启 node |
|---|---|
| 页面文件(html / css) | 不需要,刷新浏览器即可 |
| 服务器代码(app.js) | 必须 Ctrl + C 停止后重新 node app.js |
验证:启动服务器后修改 app.js,刷新浏览器观察不到变化;重启 node 后变化生效。"改了代码没效果"时,先确认是否重启过服务器。
2.5 端口占用问题
另开一个 cmd 窗口,在同一项目再次执行 node app.js,将得到 EADDRINUSE(端口被占用) 报错——一个端口同一时间只能被一个程序使用。阅读报错时先看最后一行。
3. 连接 MongoDB 数据库
3.1 完整的服务器代码
在 app.js 中补齐引入 body-parser、连接数据库、定义模型(Day 2 的 schema-demo.js 在此处合并):
var express = require("express");
var bodyParser = require("body-parser");
var mongoose = require("mongoose");
// 连接 news 数据库(与 Day 2 创建的库名保持一致)
mongoose.connect("mongodb://localhost:27017/news");
mongoose.connection.on("open", function (err) {
if (err) { console.log("数据库连接失败"); }
else { console.log("数据库连接成功"); }
});
var myapp = express();
myapp.use(bodyParser.json()); // 翻译官就位:解析 JSON 请求体
myapp.use(express.static("src")); // 托管前端页面
myapp.listen(8888);
// 模型:List 为模型名,"newsList" 为集合名,字段与 Day 2 导入的数据对应
var dataModel = mongoose.model(
"List",
{ title: String, author: String, date: String, imgSrc: String },
"newsList"
);
3.2 验证连接
Ctrl + C 停止后重新 node app.js,控制台输出"数据库连接成功"即为成功。
排错演练:将连接串改为
mongodb://localhost:27017/newsX后重启——程序不报错,但连接的是一个空库。库名拼写错误就是这样"安静地失效"。核对无误后改回news。
4. 实现新闻系统的四个接口
4.1 接口的概念与三要素
接口(API:Application Programing Interface) 是前端向后台索取或提交数据的约定,由三要素构成:请求方式 + 地址 + 数据格式。
本项目使用两种请求方式:GET(获取数据)与 POST(提交数据,新增、删除、修改均属此类)。
4.2 接口清单
| 功能 | 方式 | 地址 | 关键点 |
|---|---|---|---|
| 查询新闻列表 | GET | /news |
不带参数,返回全部 |
| 新增新闻 | POST | /news |
数据在请求体(req.body)中 |
| 删除新闻 | POST | /news/:id |
id 在地址中(req.params) |
| 修改新闻 | POST | /news/:id/update |
地址携带 id + 请求体携带新数据 |
动态路由 :id:地址中的冒号表示"此处是一个变量"。前端请求 /news/123 时,代码中 req.params.id 的值即为 "123"。
两个取值方式的区别(本讲考核点):
req.params→ 取地址中的参数(删除、修改定位文档时使用);req.body→ 取请求体中的数据(新增、修改提交内容时使用)。
body-parser 的作用:浏览器 POST 过来的 JSON 原始数据,经 body-parser 解析后成为 JS 对象,放入 req.body。
异步与 .then:查询数据库需要时间,代码使用 .then 表示"完成之后执行",回调函数中的 list 就是数据库返回的数组——与 Day 1 中 fakeNews.forEach 操作的数组结构完全一致。
验收标准:每实现一个接口,以"Compass 中的数据是否按预期变化"为唯一裁判。
4.3 接口一:查询新闻列表(GET /news)
myapp.get("/news", function (req, response) {
dataModel.find().then(function (list) {
response.json({ list: list }); // 将集合中的全部文档反馈给前端
});
});
修改 app.js 后重启。验证:浏览器访问 localhost:8888/news,能看到一大段 JSON 数据——数据库中的数据第一次以接口形式输出。
4.4 接口二:删除新闻(POST /news/:id)
myapp.post("/news/:id", function (req, response) {
var id = req.params.id; // 从地址中取出 id
dataModel.deleteOne({ _id: id }).then(function () {
response.json({ success: true });
});
});
验证(安装了 Postman/Apifox 的使用工具,请求方式选 POST):
- 从 Compass 复制一条新闻的
_id; - 向
localhost:8888/news/粘贴id发送请求; - 回到 Compass 刷新,该条文档已消失。
用 curl 测试——必须带 -X POST:本接口是 POST 方式,而浏览器地址栏只会发 GET,直接访问会得到 Cannot GET /news/xxx。可直接导入 Apifox / Postman:
curl -X POST http://localhost:8888/news/6aa0c2076950cb8d56e17b4e
返回 {"success":true},回 Compass 刷新验证文档已删除。被误删的数据可从 news.newsList.json 重新导入恢复。删除接口不需要请求体,因此 -H 与 -d 均可省略(_id 从 Compass 复制,每台机器各不相同)。
4.5 接口三:修改新闻(POST /news/:id/update)
myapp.post("/news/:id/update", function (req, response) {
var id = req.params.id; // 改哪条:地址中的 id
var newsObj = req.body; // 改成什么:请求体中的新数据
dataModel.updateOne({ _id: id }, { $set: newsObj }).then(function (res) {
response.json({ status: 200, res: res });
});
});
验证:请求体(JSON 格式)为 { "title": "接口改出来的标题" } → Compass 刷新后只有 title 变化——这正是 $set 的作用(回顾 Day 2 的整条覆盖问题)。
用 curl 测试(修改接口 = 地址携带 id + 请求体携带新数据,两者缺一不可),可直接导入 Apifox / Postman:
curl -X POST http://localhost:8888/news/6aa0c2076950cb8d56e17b4e/update -H "Content-Type: application/json" -d '{"title":"接口改出来的标题"}'
与新增接口的 curl 写法相比只有两处不同:地址末尾多了 /update(对应动态路由),请求体只写要改的字段。返回 {"status":200,...} 后去 Compass 刷新,确认只有 title 变了,其余字段原样保留。
4.6 接口四:新增新闻(POST /news)
myapp.post("/news", function (req, response) {
var newsObj = req.body;
dataModel.create(newsObj).then(function (news) {
response.json({ news: news }); // 将创建成功的文档(含新 _id)反馈给前端
});
});
验证:请求体提供 date、title、author、imgSrc 四个字段 → Compass 刷新后多出一条文档,且自动获得了新的 _id。
没有安装 Postman/Apifox 的同学,用 curl 也能测试本接口。下面这条 curl 采用标准格式,可直接用于 Postman / Apifox 的 导入 curl 功能(导入 → 导入数据 → 选择 cURL,粘贴整条命令,即可自动生成带地址、请求头与 JSON 请求体的完整请求):
curl -X POST http://localhost:8888/news -H "Content-Type: application/json" -d '{"title":"测试新闻","author":"张三","date":"2026-09-09 10:00","imgSrc":""}'
逐段拆解:
-X POST:指定请求方式为 POST;-H "Content-Type: application/json":设置请求头,告诉服务器请求体是 JSON——漏掉它,req.body就是 undefined(对照 5.3 排查表);-d:请求体数据,一个 JSON 对象,字段名与模型完全一致(title / author / date / imgSrc)。导入 Apifox/Postman 时 JSON 保持原样即可,无需任何转义;- 只有在 Windows cmd 中直接执行时,JSON 内的双引号才需要写成
\"转义。
成功的标志:返回的 JSON 中 news 字段带有 _id → 回 Compass 刷新 newsList 集合,多出这条文档 → 浏览器访问 localhost:8888/news,列表中也出现它。建议把这条 curl 导入 Apifox/Postman 后保存为接口文档(5.1 进阶选做任务 ②),其余三个接口照此办理。
注意:查询接口(GET)不需要 curl,浏览器直接打开
localhost:8888/news即可;删除与修改接口的 curl 写法见 4.4、4.5 节。
4.7 收尾口诀
接口四兄弟:find 查、create 增、deleteOne 删、updateOne 改; 改哪条看
params,改什么看body; 写完接口必测试,Compass 是裁判。
5. 动手练习
5.1 任务清单
| 层级 | 任务 |
|---|---|
| 基础必做 | ① 独立完成 npm init、安装依赖、托管页面(/tmt、/system 可访问);② 连接数据库并输出"数据库连接成功";③ 完成查询、新增 2 个接口并测试通过 |
| 进阶选做 | ① 补齐删除、修改接口,4 个接口全部测试通过;② 用 Apifox/Postman 保存 4 个请求,形成自己的接口文档;③ 排错练习:教师群发 3 份"故障 app.js"(① 连库地址写成 newsX ② 漏写 bodyParser ③ 端口被占),定位并修复 |
| 挑战加分 | ① 新增模糊查询接口 GET /news/search?kw=芯片(提示:find({ title: { $regex: req.query.kw } }));② 新增分页接口 GET /news/page?page=1&size=5(提示:skip / limit);③ 思考题(写入日志):删除操作为什么用 POST 而不是标准 REST 的 DELETE 方法? |
5.2 自查清单
- [ ] 4 个接口均能以 Compass 中的数据变化作为通过证据
- [ ] 每次修改 app.js 后都重启了 node
- [ ] 控制台无报错
- [ ] MongoDB 服务始终在运行(后台服务,无需窗口)
5.3 常见问题排查
| 现象 | 常见原因 |
|---|---|
Cannot find module 'express' |
未安装依赖,或在错误的目录中安装(必须回到 C:\news) |
| 页面 404 | static 路径不对,或文件夹名大小写不一致 |
| 连接"成功"但查询不到数据 | 库名 / 集合名拼写错误(核对 news、newsList) |
req.body 为 undefined |
忘记 myapp.use(bodyParser.json()),或该语句写在了接口之后 |
| 接口长时间无响应 | MongoDB 服务未启动(管理员 cmd 执行 net start MongoDB) |
| 修改 app.js 后无效果 | 未执行 Ctrl + C 后重新 node app.js |
6. 环境准备(下一讲:完整项目与答辩)
下一讲进行项目总览、前后端联调与限时复刻答辩,请在课程开始前逐项完成以下准备,并在群内打卡"Day4 环境打卡":
- 代码归档:确认
C:\news中app.js、package.json、src齐全且当前能正常运行;将"4 个接口测试通过"的截图(工具界面或与 Compass 的对照)发到群内; - 数据就绪:启动数据库,newsList 集合文档数 ≥ 10(不足的从
news.newsList.json重新导入); - 前端页面就绪:使用 Day 1 自己编写的页面即可;如需完善,可用教师发放的
digi/src成品前端覆盖(下一讲将逐行精讲该代码,使用自写版本理解更深); - 自测联通:执行
node app.js后依次访问以下三个地址,全部正常后截图发群: -localhost:8888/tmt-localhost:8888/system-localhost:8888/news - 分组准备(答辩计入成绩):2–3 人一组,复刻环节每组进行 3 分钟验收答辩——提前确定分工:谁演示功能、谁讲架构、谁讲踩坑;
- 思考题:后台点击"删除"后,页面上的那一行是如何消失的?数据库中的那条数据又是如何消失的?(两个"消失"的路径并不相同)
7. 学习日志
# 学习日志 Day 3 姓名:____ 日期:____
1. 本讲我学会了:____(3 条以内,写具体的代码/概念)
2. 我遇到的问题与解决:____(什么现象 → 怎么定位 → 怎么解决;未解决也如实记录)
3. 我想进一步弄明白:____
4. 环境准备打卡:已完成 / 未完成(卡在哪一步:____)
8. 本讲小结
- Node.js 与 npm:Node 让 JS 运行在服务器上;npm 安装依赖,package.json 记录清单;
- Express 三件事:创建服务、托管页面(static)、监听端口(listen);修改服务器代码必须重启;
- 接口三要素:方式 + 地址 + 数据;
req.params取地址参数,req.body取请求体数据; - 四个接口四条命令:find 查、create 增、deleteOne 删、updateOne 改。
举手提问