CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
MongoDB 入门/课时/Node.js 驱动与 Mongoose
RawBlame
课时目录12 节

MongoDB 入门

  1. 01MongoDB 是什么
  2. 02安装与 mongosh
  3. 03文档与 BSON
  4. 04CRUD 操作
  5. 05查询与投影
  6. 06索引
  7. 07聚合管道
  8. 08Spring Data MongoDB
  9. 09何时用 Mongo vs SQL
  10. 10Docker 运行 MongoDB
  11. 11Node.js 驱动与 Mongoose
  12. 12综合练习

Node.js 驱动与 Mongoose

mongoose schema、与 nodejs 路线图衔接。

30 分钟

Node.js 驱动与 Mongoose

这一课你将能做到

  1. 用官方 mongodb 驱动完成连接与 insertOne / find,理解连接生命周期(connect / close)
  2. 用 Mongoose 定义 Schema 与 Model,利用 required / enum 校验脏数据,用 lean() 优化只读查询
  3. 对照 Spring Data MongoDB 的概念表,说清 Node 直连在本站架构中的位置:脚本 / BFF,不是主 API 替代品

为什么要学

全栈同学常在 Node 侧写数据导入脚本、定时任务或轻量 BFF。这里有两层工具:官方驱动(mongodb 包)给最底层的 CRUD API,适合脚本与精细控制;Mongoose 在其上加 Schema 层,把「约定字段形状」写进代码——相当于应用层 schema。

本课的意义不止 Node:通过对比两种风格(裸驱动 vs ODM),你会同时理解 Spring 里 MongoTemplate 与 @Document 实体的分工——同一套思想,两种语言。

人话:驱动 = 拿着 Mongo 的遥控器直接按键;Mongoose = 先写好「数据长什么样」的模板,不合格的数据进不来。

先建立图像

Next.js platform → Spring Boot API → MySQL(权威)
                        ↓ 可选
        Node 脚本 / BFF → MongoDB(导入、日志、实验)
           ├─ mongodb 驱动:底层 API,脚本用
           └─ Mongoose:Schema/Model,业务用

概念对照:

Node.jsSpring Bootmongosh
Schema 字段定义@Document 实体字段(无强制 schema)
mongoose.model()MongoRepositorydb.集合
.find() / .create()findBy… / savefind / insertOne
.populate()$lookup / @DBRef$lookup
连接池(驱动内置)自动装配客户端单连接会话

跟着做

步骤 1:准备项目与依赖

mkdir mongo-node-lab && cd mongo-node-lab
npm init -y
npm install mongodb mongoose
# package.json 里加 "type": "module" 以支持 ESM 语法

确认 Docker 里 Mongo 已启动,URI 复用前面课的:

mongodb://admin:secret@127.0.0.1:27017/coderoadmap_lab?authSource=admin

真实项目把 URI 放进 .env(MONGO_URI=...),代码里 process.env.MONGO_URI——下面演示为了可复制仍写明文,提交前必须替换。

步骤 2:官方驱动最小 CRUD

新建 driver-demo.mjs:

import { MongoClient } from "mongodb";

const uri = "mongodb://admin:secret@127.0.0.1:27017/coderoadmap_lab?authSource=admin";
const client = new MongoClient(uri);

async function main() {
  await client.connect();
  const col = client.db().collection("lesson_logs");   // URI 里已指定库

  const r = await col.insertOne({
    lessonSlug: "mongo-node",
    userId: "u_1001",
    action: "view",
    at: new Date()
  });
  console.log("inserted:", r.insertedId);

  const list = await col.find({ userId: "u_1001" })
    .sort({ at: -1 })
    .limit(5)
    .toArray();
  console.log(list);
}

main()
  .catch((e) => { console.error(e); process.exitCode = 1; })
  .finally(() => client.close());   // 脚本必须收尾关连接
node driver-demo.mjs

应看到:打印 inserted 的 ObjectId 与文档数组;mongosh 里 find 能对上。注意 find() 返回游标,必须 .toArray() 或迭代才拿到数据。

步骤 3:Mongoose Schema 与 Model

新建 mongoose-demo.mjs:

import mongoose from "mongoose";

await mongoose.connect(
  "mongodb://admin:secret@127.0.0.1:27017/coderoadmap_lab?authSource=admin"
);

const logSchema = new mongoose.Schema({
  lessonSlug: { type: String, required: true, index: true },
  userId:     { type: String, required: true },
  action:     { type: String, enum: ["view", "complete"] },
  at:         { type: Date, default: Date.now }
}, { versionKey: false });

// 第三个参数显式指定集合名,避免默认复数规则改名
const LessonLog = mongoose.model("LessonLog", logSchema, "lesson_logs");

await LessonLog.create({
  lessonSlug: "mongo-node",
  userId: "u_1002",
  action: "view"
});

const list = await LessonLog
  .find({ userId: "u_1002" })
  .sort({ at: -1 })
  .lean();          // 只读场景返回普通对象,跳过 Document 封装,更快更省

console.log(list);
await mongoose.disconnect();

应看到:合法数据入库可查。

步骤 4:故意触发校验错误

把 action: "view" 改成 "click" 再跑:

ValidationError: LessonLog validation failed: action: `click` is not a valid enum value

required、enum、type 都在写入/更新前拦截脏数据——这正是 ODM 相对裸驱动的核心价值:schema 约束落在代码里,评审有据可查。

步骤 5:与 Spring 实体对齐字段契约

打开 Spring 课的 LessonLog 实体,逐字段对照本课 Schema:lessonSlug、userId、action、at。两端读写同一集合时,字段名与类型完全一致比谁先写入更重要——这就是跨语言协作的「字段契约」。

步骤 6:写清架构边界三句话

  1. 前端 platform 走 Spring REST API,浏览器绝不直连 Mongo(URI 与凭据只活在服务端)
  2. Node 直连适用于脚本、迁移、内部工具、轻量 BFF
  3. 权威课程数据仍在 MySQL cf_track,任何语言都不例外

讲清楚要点

  1. 驱动与 Mongoose 是两层:驱动给 API(脚本、一次性任务),Mongoose 给 schema 约束(业务模型);同一项目混用可以,但集合的字段契约必须唯一。
  2. 连接生命周期自己负责:脚本要 finally 里 close()/disconnect();常驻服务用全局单例连接(驱动内置连接池),不要每请求新建。
  3. Mongoose 默认复数化集合名:model("LessonLog") 默认找 lessonlogs——第三个参数显式指定 lesson_logs 是防漂移的关键一行。
  4. lean() 换性能:只读列表跳过 Document 封装与 change tracking;需要 save 回写的文档别 lean。
  5. 校验在应用层:required/enum 拦的是应用写入;mongosh 或其他语言直写不受它管——数据库层的唯一约束仍要靠索引(如唯一索引)。
  6. 与 Spring 共享集合靠契约:字段名、类型、枚举值三对齐,任何一端单独改字段都是破坏性变更,要走评审。

你可能会懵的地方

  • 集合里已有异构文档,Mongoose 会怎样? 读出来的文档缺字段就是 undefined、类型不符会按 strict 模式处理;Schema 只约束「经过 Model 的读写」。所以先有数据再写 Schema 时要 strict: false 或逐字段核对。

  • 装了 mongoose 还要装 mongodb 吗? mongoose 内部依赖官方驱动会自动装上;想直接用底层 API 才显式 npm install mongodb。两个 demo 分开体验即可。

  • populate() 是 JOIN 吗? 是应用层关联填充:按存的 _id 再查一次另一集合拼回来。本质多次查询(或驱动聚合优化),滥用同样有 N+1 问题——与 Spring 的 $lookup 决策逻辑一致,优先内嵌。

  • 为什么示例代码里写明文密码? 为了本课可复制。真实项目必须 .env + dotenv,.env 进 .gitignore;泄露到 git 的密码即使删除提交也视为已泄露,要立刻轮换。

  • Express 服务里怎么组织连接? 启动时 mongoose.connect() 一次,全局复用默认连接;关闭时在 SIGTERM 里 disconnect。不要每个路由里 connect。

错法对照

错法问题正解
脚本跑完不 close()/disconnect()进程挂起、连接堆积finally 收尾关闭
依赖 model 默认集合名得到 lessonlogs 而非 lesson_logs第三个参数显式指定
Schema 字段名与 Spring 端不一致两端各写各的,数据互相看不见先定契约再写代码
前端 / 浏览器拿到带密码的 URI凭据泄露 + 任意读写URI 只在服务端;用户走 API
关掉校验「图省事」脏数据堆积、统计失真保留 required / enum
每个请求 new MongoClient + connect连接风暴全局单例 + 驱动连接池

卡点急救

  1. 现象:脚本执行完进程不退出 连接没关。把后续逻辑放 main().finally(() => client.close());Mongoose 用 mongoose.disconnect()。常驻服务则相反——不要关。
  2. 现象:Model 查不到 mongosh 明明有的数据 三查:集合名是否被复数化改写(打印 LessonLog.collection.name);库名对不对(URI 里 /coderoadmap_lab);字段名是否与库里一致。逐项打印核对。
  3. 现象:写入报 ValidationError 但值看起来合法 检查 enum 列表是否遗漏该值、类型是否匹配(字符串 vs 数字)、是否触发了自定义 validator;先 doc.validate() 单独定位是哪个字段。

面试常问

  • Mongoose 的作用是什么?不用它直接用驱动行不行? Mongoose 是 ODM:Schema 定义形状、应用层校验、Model 封装读写、中间件(钩子)与 populate 关联。裸驱动更轻更灵活,适合脚本与追求极致控制的场景;业务模型复杂时 ODM 的约束与可维护性收益更大——两层怎么选取决于项目形态。

  • Node 侧如何管理 MongoDB 连接? 驱动内置连接池:常驻服务在启动时建立一次连接并全局复用(mongoose 的默认连接即单例);脚本任务用完在 finally 关闭。反模式是每请求新建连接——握手开销与句柄耗尽都是线上事故来源。

  • Mongoose Schema 和 MongoDB 的灵活 schema 矛盾吗? 不矛盾,是分层:库层不强制( mongosh / 其他语言可自由写入),应用层通过 Schema 收紧本服务的写入形状。这是「库层灵活、应用层强约定」原则在 Node 生态的落地——面试答出分层即可。

自测三问

  1. 官方驱动与 Mongoose 各适合什么场景?
  2. mongoose.model("LessonLog", schema, "lesson_logs") 第三个参数为什么必须写?
  3. Node 直连 MongoDB 在本站架构中的推荐位置是什么?

小结一句话

mongodb 驱动给底层 CRUD(脚本利器),Mongoose 用 Schema/Model 约束数据形状(业务利器);二者通过字段契约与 Spring 端共享集合,但不改变 MySQL 权威主路径。

延伸

  • MongoDB Node.js Driver 文档
  • Mongoose 官方指南
  • 下一课:综合练习——把全 track 串成一条可演示的完整链路。
加载本节练习…
← 上一课Docker 运行 MongoDB下一课 →综合练习