尧图精选

MongoDB和mongoose学习笔记:从连接配置到Schema建模的完整实践

🕒 发布时间:2026/10/1 7:36:08 📁 来源:尧图网络
1. 从一次本地数据层搭建说起MongoDB 与 mongoose 到底解决什么问题如果你正在写一个 Node.js 项目数据要存起来又不想被 MySQL 那种先建表、定字段、改字段还要迁移的结构绑住手脚那 MongoDB 加 mongoose 这套组合大概率就是你要找的东西。MongoDB 是一个文档型数据库数据以类似 JSON 的文档形式存储一个集合里的每条记录字段可以不一样改结构不用停机迁移mongoose 则是站在 MongoDB 之上的对象文档模型ODM库它把「连接、Schema、Model、CRUD」这些动作封装成一套在 Node.js 里写起来很顺手的 API。简单说MongoDB 负责存mongoose 负责让你用 JS 对象的方式去操作它。这套东西适合谁适合刚接触后端、想快速跑通增删改查的前端同学适合做小工具、原型、爬虫落库、内容管理这类结构不固定的项目也适合已经会写 SQL但想理解文档数据库建模思路的开发者。我自己第一次用的时候最大的感受是「不用先设计表」先把数据塞进去跑起来再慢慢收敛 Schema这种节奏对快速验证特别友好。这篇笔记按一条完整链路走先讲清楚 MongoDB 的核心概念和本地服务怎么起再进入 mongoose 的连接配置、Schema 与 Model 定义然后是 CRUD 和关联查询最后把常见的连接报错、字段类型坑、查询结果异常挨个排一遍。每一步都给可复制的代码和验证动作你跟着敲就能在本地跑通一个能用的数据层。中间涉及模型调用的部分我会用 TaoToken 的接口做一次真实请求验证把「配置 → 发请求 → 看返回」这条链路也走通这样你不仅会写本地代码也知道怎么把模型能力接进自己的项目里。先明确一个心智模型MongoDB 服务mongod是一个进程它管理多个数据库一个数据库里有多个集合collection一个集合里有多个文档document文档就是一条条 BSON 格式的记录。mongoose 里的 Schema 描述文档长什么样Model 是操作某个集合的入口Document 是查出来或新建的单条记录。把这四层关系记住后面所有 API 都不会迷路。2. 本地起服务与核心概念mongod、集合、文档与 BSON 的对应关系在写 mongoose 之前得先让 MongoDB 服务跑起来。Windows 上安装完 MongoDB 后通常已经注册成了系统服务直接在管理员命令行执行net start mongodb就能启动。如果你改了端口或者想换数据目录先sc delete MongoDB删掉旧服务改完配置文件再重新注册启动。macOS 用 Homebrew 的话是brew services start mongodb-communityLinux 用systemctl start mongod。启动后用mongo或新版mongosh连上去默认本地无密码直连。连上之后先熟悉几个命令这些命令和后面 mongoose 的概念是一一对应的。show dbs列出所有数据库注意它只显示有数据的库use myapp切换或创建数据库库不存在时会自动创建但只有插入数据后才会真正出现在列表里db显示当前所在库db.dropDatabase()删除当前库。集合层面db.createCollection(users)显式创建集合也可以不创建第一次插入文档时自动生成show collections查看集合db.users.drop()删除集合。文档层面插入用db.users.insertOne({name:fjx, age:21})或db.users.insertMany([...])。这里有个关键点MongoDB 存储的是 BSON也就是 Binary JSON它比普通 JSON 多了日期、ObjectId、二进制等类型。每条文档会自动生成一个_id字段类型是 ObjectId这是主键全局唯一。查询用db.users.find({age:{$gte:20}})比较运算符要加$前缀$gt大于、$lt小于、$gte大于等于、$lte小于等于、$ne不等于、$eq等于。逻辑运算用$and、$or、$in比如db.users.find({$or:[{age:18},{age:24}]})。更新用updateOne和updateMany注意不加$set会整条覆盖加了才只改指定字段。删除官方推荐deleteOne和deleteManydb.users.deleteMany({})清空集合。理解这些命令后再看 mongoose 就不会觉得它是凭空冒出来的一套东西。mongoose 的Model.create对应insertOneModel.find对应findModel.updateOne对应updateOne只是它把 BSON 的细节包起来让你用 JS 对象和 Promise 来写。下面这张对照表可以贴在旁边随时看MongoDB 命令mongoose 方法说明insertOne/insertManyModel.create/Model.insertMany插入文档find/findOneModel.find/Model.findOne查询返回 Document 实例updateOne/updateManyModel.updateOne/Model.updateMany更新需$setdeleteOne/deleteManyModel.deleteOne/Model.deleteMany删除_id(ObjectId)_id(Schema.Types.ObjectId)主键关联查询靠它注意show dbs看不到刚use的库是正常的因为库还没数据。插入一条文档后再执行就能看到。3. mongoose 连接配置与 Schema 建模可复制的 settings 片段进入正题。先初始化项目并安装依赖npm init -y然后npm i mongoose。mongoose 是纯 JS 库不需要编译工具装完就能用。接下来是连接配置这一步最容易出问题我把本地无密码和带密码两种写法都给你。本地无密码最简写法const mongoose require(mongoose); mongoose.connect(mongodb://127.0.0.1:27017/myapp) .then(() console.log(MongoDB connected)) .catch(err console.error(connect error:, err));如果数据库开了认证连接串要带用户名密码和authSourcemongoose.connect(mongodb://admin:yourPassword127.0.0.1:27017/myapp?authSourceadmin) .then(() console.log(MongoDB connected)) .catch(err console.error(connect error:, err));这里authSourceadmin很关键用户是在 admin 库创建的不写这个参数会报认证失败。连接成功后mongoose 默认会缓存模型操作也就是说即使连接还没建立你调用Model.create也不会立刻报错而是等连接好了再执行。这个特性有时会掩盖连接问题所以建议在启动时显式监听error事件。Schema 是 mongoose 的核心它定义文档结构和字段类型。下面是一个用户 Schema覆盖常见类型和校验const userSchema new mongoose.Schema({ userName: { type: String, required: true, trim: true }, password: { type: String, required: true, select: false }, email: { type: String, unique: true, lowercase: true }, age: { type: Number, min: 0, max: 150 }, tags: [String], profile: { city: String, bio: String }, createdAt: { type: Date, default: Date.now } }, { timestamps: true }); const User mongoose.model(User, userSchema);几个要点required必填unique唯一索引注意它建的是索引不是校验器重复插入会报 E11000select: false让密码默认查不出来需要时用.select(password)。timestamps: true会自动加createdAt和updatedAt。mongoose.model(User, userSchema)第一个参数是模型名mongoose 会自动把它转成复数集合名users如果你想要别的集合名第三个参数可以指定。关联查询靠ref和populate。比如文章属于某个用户const postSchema new mongoose.Schema({ title: String, content: String, author: { type: mongoose.Schema.Types.ObjectId, ref: User } }); const Post mongoose.model(Post, postSchema);查询时Post.find().populate(author)就会把author字段从 ObjectId 替换成对应的用户文档。这是文档数据库里模拟关联的标准做法底层是两次查询不是 SQL 的 join。如果你要把模型能力接到自己的服务里做验证可以在项目里加一个调用入口。TaoToken 的接口地址是https://taotoken.net/api模型对话入口在https://taotoken.net/modelsAPI Key 在https://taotoken.net/api-keys管理。下面这段配置可以直接放进你的config.js// config.js module.exports { mongoUri: mongodb://127.0.0.1:27017/myapp, taoToken: { baseUrl: https://taotoken.net/api, apiKey: process.env.TAOTOKEN_API_KEY, model: claude-sonnet-4-5 } };把 Key 放环境变量不要硬编码进仓库。Base URL、Key、Model ID 这三件套配齐后面发请求才不会 401。4. 跑通 CRUD 与关联查询从 create 到 populate 的验证请求配置好了就动手验证。先写一个app.js把连接、建模、增删改查串起来const mongoose require(mongoose); const { mongoUri } require(./config); async function main() { await mongoose.connect(mongoUri); console.log(connected); const userSchema new mongoose.Schema({ userName: String, age: Number, tags: [String] }); const User mongoose.model(User, userSchema); // 新增 const created await User.create({ userName: fjx, age: 21, tags: [dev] }); console.log(created:, created._id); // 批量新增 await User.insertMany([ { userName: zyw, age: 20 }, { userName: zt, age: 18 } ]); // 查询 const adults await User.find({ age: { $gte: 20 } }).sort({ age: -1 }); console.log(adults:, adults.map(u u.userName)); // 更新 await User.updateOne({ userName: fjx }, { $set: { age: 22 } }); // 删除 await User.deleteOne({ userName: zt }); await mongoose.disconnect(); } main().catch(err console.error(err));运行node app.js你应该看到 created 的 ObjectId 和 adults 列表。这里有几个验证点create返回的是 Document 实例带_idfind返回数组每个元素也是 Document可以直接.userName访问updateOne返回{ matchedCount, modifiedCount }不是更新后的文档要拿新文档得用findOneAndUpdate加{ new: true }。关联查询验证const post await Post.create({ title: hello, content: world, author: created._id }); const populated await Post.findOne({ title: hello }).populate(author); console.log(populated.author.userName); // fjxpopulate之后author从 ObjectId 变成了完整用户对象。如果只想取部分字段可以.populate(author, userName)。现在把模型调用也验证一下。用 Node 18 自带的 fetch 发一个请求async function askModel() { const res await fetch(https://taotoken.net/api/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.TAOTOKEN_API_KEY} }, body: JSON.stringify({ model: claude-sonnet-4-5, messages: [{ role: user, content: 用一句话解释 MongoDB 文档模型 }] }) }); const data await res.json(); console.log(data.choices[0].message.content); }跑通后你会看到模型返回的一句话解释。这一步的意义是你的数据层和模型调用层都通了后面做内容生成、自动打标签这类功能就是把User.create和askModel组合起来。如果你要长期跑编码类任务或 Agent可以了解下 Coding Plan入口在https://taotoken.net/coding-plan需要管理多个 Key 或看用量控制台在https://taotoken.net/console。5. 常见报错排查401、local proxy failed、reading choices 与 OAuth这一节把踩过的坑集中列出来对照你的报错直接找。MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017。这是本地服务没起。Windows 执行net start mongodbmacOSbrew services start mongodb-communityLinuxsystemctl start mongod。如果服务起了还报这个检查连接串端口是不是被改了默认 27017。MongoServerError: Authentication failed。连接串带了用户名密码但认证失败。检查三点用户名密码是否正确authSource是否指向创建用户的库通常是 admin密码里有没有特殊字符需要 URL 编码。用mongosh -u admin -p手动连一次能快速定位。E11000 duplicate key error。Schema 里设了unique: true插入了重复值。注意unique是索引约束不是校验器报错发生在写入时。处理方式是捕获错误码 11000 做友好提示或者写入前先查一次。ValidationError: Pathxxxis required。必填字段没传。检查create或save的入参required: true的字段一个都不能少。CastError: Cast to ObjectId failed。用字符串去查 ObjectId 字段或者传了非法 id。用mongoose.Types.ObjectId.isValid(id)先判断或者用findById时确保 id 是 24 位十六进制字符串。调用模型接口返回 401。这是 Key 的问题。检查Authorization头是不是Bearer加 Key中间有空格Key 是否复制完整环境变量是否真的加载了console.log(process.env.TAOTOKEN_API_KEY)确认。Key 在https://taotoken.net/api-keys重新生成一个再试。local proxy failed 或连接超时。这类报错通常是网络出口问题。检查你的运行环境是否能正常访问外网公司网络可能需要配置白名单。不要用任何非正规的网络工具直接确认目标域名可达即可。Cannot read properties of undefined (reading choices)。这是解析返回时data.choices不存在。先console.log(data)看真实返回常见原因是请求体格式不对比如messages写成了字符串、模型名写错、或者返回的是错误对象。把res.status也打出来401 和 400 都会走到这里。OAuth 相关报错。如果你用的是需要 OAuth 授权的客户端工具检查 token 是否过期、回调地址是否配置正确。这类问题优先看工具自己的日志确认授权流程走到哪一步断了。populate 返回 null。关联的_id在目标集合里找不到对应文档。检查ref的模型名是否和mongoose.model注册的一致以及被关联的文档是否真的存在。提示排查连接类问题先单独写一个最小脚本只做mongoose.connect排除业务代码干扰。跑通了再往里加模型。6. 把数据层接进你的项目下一步可以怎么走到这里你已经有了一个能跑的本地数据层MongoDB 服务起着mongoose 连着Schema 定义好了CRUD 和关联查询都验证过模型调用也通了。接下来可以做的方向有几个。一是把连接和模型拆到独立文件用models/目录管理db.js统一导出连接二是给常用查询加索引比如userSchema.index({ email: 1 })数据量上来后查询会快很多三是把模型调用封装成服务函数比如generateTags(content)在写入文档时自动打标签。如果你要长期做编码类任务或者搭 AgentCoding Plan 的入口在https://taotoken.net/coding-plan接入文档在https://taotoken.net/doc里面有更完整的参数说明和示例。模型对话可以直接在https://taotoken.net/models里试不用写代码就能验证返回格式。API Key 管理在https://taotoken.net/api-keys控制台在https://taotoken.net/console。最后留一个实用习惯每次改完 Schema先在测试库跑一遍Model.create和Model.find确认字段类型和默认值符合预期再往生产库同步。文档数据库的灵活性是优势但也意味着结构约束靠你自己守Schema 就是你守结构的那道门。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →