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):

  1. 从 Compass 复制一条新闻的 _id
  2. localhost:8888/news/粘贴id 发送请求;
  3. 回到 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 环境打卡":

  1. 代码归档:确认 C:\newsapp.jspackage.jsonsrc 齐全且当前能正常运行;将"4 个接口测试通过"的截图(工具界面或与 Compass 的对照)发到群内;
  2. 数据就绪:启动数据库,newsList 集合文档数 ≥ 10(不足的从 news.newsList.json 重新导入);
  3. 前端页面就绪:使用 Day 1 自己编写的页面即可;如需完善,可用教师发放的 digi/src 成品前端覆盖(下一讲将逐行精讲该代码,使用自写版本理解更深);
  4. 自测联通:执行 node app.js 后依次访问以下三个地址,全部正常后截图发群: - localhost:8888/tmt - localhost:8888/system - localhost:8888/news
  5. 分组准备(答辩计入成绩):2–3 人一组,复刻环节每组进行 3 分钟验收答辩——提前确定分工:谁演示功能、谁讲架构、谁讲踩坑;
  6. 思考题:后台点击"删除"后,页面上的那一行是如何消失的?数据库中的那条数据又是如何消失的?(两个"消失"的路径并不相同)

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 改。