ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nodejs学习笔记(十四)— Mongoose介绍和入门:用TaoToken统一Key跑通Schema建模与CRUD验证

Nodejs学习笔记(十四)— Mongoose介绍和入门:用TaoToken统一Key跑通Schema建模与CRUD验证 1. 从 node-mongodb-native 到 Mongoose为什么你的 CRUD 代码越写越乱如果你跟着 Nodejs 学习笔记一路写到这里大概率已经用node-mongodb-native跑通过一次 MongoDB 的增删改查。那个阶段的感觉通常是能跑但代码丑。每次操作都要MongoClient.connect回调一层套一层字段校验全靠自己if判断集合名和字段名散落在十几个文件里改一个字段名要全局搜索替换。Mongoose 解决的就是这个问题。它是 Node.js 异步环境下对 MongoDB 进行便捷操作的对象模型工具核心价值在于把「集合」抽象成 Schema把「文档」抽象成 Model让你用面向对象的方式操作数据库。你可以把它理解成 MongoDB 世界的 ORM 轻量版Schema 定义表结构Model 负责数据读写中间还自带类型转换、默认值、校验器和中间件。这篇文章面向刚接触 MongoDB 的 Node 开发者场景是本地 Node 项目从零搭建 Mongoose 连接、定义 Schema 与 Model完成增删改查与数据校验。同时我会把调用凭证统一走 TaoToken 的 API 通道管理避免 Key 散落在.env、config.js、settings.json里到处复制。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。先说清楚 Mongoose 和原生驱动的分工。原生驱动mongodb包给你的是最底层的collection.find()、collection.insertOne()它不关心你的数据结构长什么样。Mongoose 在它之上加了三层东西第一层是 Schema声明字段类型、是否必填、默认值、索引第二层是 Model把 Schema 编译成可操作的构造函数提供save、find、findByIdAndUpdate等方法第三层是 Document每个查询结果都是带方法的文档实例可以直接.save()回写。适合谁看已经会npm init、能跑node xxx.js、本地装过 MongoDB 或者用 Docker 起过 MongoDB 的开发者。如果你还没装 MongoDBWindows 下可以用mongod --config方式注册服务Mac 下brew install mongodb-community然后brew services start mongodb-communityDocker 下docker run -d -p 27017:27017 --name mongo mongo:7。端口默认 27017本文所有示例都基于mongodb://localhost:27017/mongoosesample。我试过在同一个项目里混用原生驱动和 Mongoose结果是连接池管理混乱、回调风格和 Promise 风格交织最后全部重写成 Mongoose。所以建议你从这一篇开始新项目直接上 Mongoose老项目逐步迁移。2. TaoToken 前置统一 Key 与 API 通道的可复制配置在写 Mongoose 代码之前先把调用凭证这件事理清楚。很多人的 Node 项目里MongoDB 连接串、第三方 API Key、模型调用凭证混在一个.env文件里时间一长自己都分不清哪个 Key 对应哪个服务。TaoToken 的作用是提供一个统一的 API 通道把模型对话、coding-plan、console、api-keys 这些入口收敛到一套 Key 管理下。你需要先拿到自己的 API Key。登录后进入控制台在 API Keys 页面创建一个新 Key复制出来。这个 Key 后面会用在两个地方一是 Node 项目里通过环境变量读取二是如果你用 Claude Code 或 Cline 这类工具填到对应的配置里。先看 Node 项目里的配置方式。在项目根目录创建.env文件写入TAOTOKEN_API_KEYsk-你的实际Key TAOTOKEN_BASE_URLhttps://taotoken.net/api MONGODB_URImongodb://localhost:27017/mongoosesample注意TAOTOKEN_BASE_URL后面不要加 UTM 参数API 调用地址就是干净的https://taotoken.net/api。然后在package.json同级创建config/taotoken.js// config/taotoken.js require(dotenv).config(); const taotokenConfig { apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL, // 统一超时与重试避免每个调用点各写一套 timeout: 30000, retries: 2, }; if (!taotokenConfig.apiKey) { throw new Error(缺少 TAOTOKEN_API_KEY请检查 .env 文件); } module.exports taotokenConfig;如果你用的是 Claude Code配置方式不太一样。Claude Code 读取的是 settings 文件通常在~/.claude/settings.json或项目级.claude/settings.json。写入以下 JSON{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的实际Key } }这里三个要素必须齐全Base URL 填https://taotoken.net/apiKey 填你创建的那串Model ID 在 Claude Code 里通过/model命令选择或者在 settings 里加model: claude-sonnet-4-20250514这类具体 ID。缺任何一个都会导致请求发不出去。如果你用 Cline 并且配了 MCPMCP 的配置也是同样的三件套。在 Cline 的 MCP 设置里Base URL、API Key、Model ID 分别填好不要只填 Key 就以为能通。Codex 的auth.json同理路径通常在~/.codex/auth.json里面需要包含 base_url 和 api_key 字段。为什么要在 Mongoose 教程里讲这些因为一个真实的 Node 项目往往不只是连数据库还要调用模型做数据处理、字段补全、内容审核。把这些调用统一走 TaoToken 的通道Key 只维护一份换 Key 的时候只改一个地方。Mongoose 负责数据持久化TaoToken 负责外部调用凭证职责分开排障的时候不会互相干扰。3. Mongoose 连接、Schema 与 Model从 db.js 到 user.js 的完整落地现在进入 Mongoose 本体。先安装npm install mongoose dotenv安装完成后创建db.js。这个文件只做一件事建立连接并导出 mongoose 实例。注意 Mongoose 7 之后connect返回 Promise不再需要回调但连接事件监听仍然有用方便你在控制台看到连接状态。// db.js const mongoose require(mongoose); const DB_URL process.env.MONGODB_URI || mongodb://localhost:27017/mongoosesample; mongoose.connect(DB_URL, { // 新版驱动不再需要 useNewUrlParser但保留 serverSelectionTimeoutMS 便于快速失败 serverSelectionTimeoutMS: 5000, }); mongoose.connection.on(connected, () { console.log(Mongoose connection open to DB_URL); }); mongoose.connection.on(error, (err) { console.log(Mongoose connection error: err); }); mongoose.connection.on(disconnected, () { console.log(Mongoose connection disconnected); }); module.exports mongoose;执行node db.js如果 MongoDB 在跑你会看到Mongoose connection open to mongodb://localhost:27017/mongoosesample。如果报MongooseServerSelectionError先检查 MongoDB 服务是否启动再检查端口和连接串。接下来定义 Schema。创建models/user.js// models/user.js const mongoose require(../db.js); const Schema mongoose.Schema; const UserSchema new Schema({ username: { type: String, required: [true, 用户名不能为空], index: true, trim: true, }, userpwd: { type: String, required: true, minlength: [6, 密码至少6位], }, userage: { type: Number, min: [0, 年龄不能为负], max: [150, 年龄超出合理范围], }, email: { type: String, match: [/^\S\S\.\S$/, 邮箱格式不正确], }, logindate: { type: Date, default: Date.now, }, }, { collection: users, // 显式指定集合名避免 Mongoose 自动复数化带来的困惑 timestamps: true, // 自动维护 createdAt 和 updatedAt }); module.exports mongoose.model(User, UserSchema);这里有几个点值得展开。required后面可以跟数组第二个元素是自定义错误信息校验失败时err.errors.username.message就是这句话。index: true会在 username 上建索引查询频繁的字段建议加上。default: Date.now让 logindate 在插入时自动填充不用每次手动new Date()。timestamps: true是 Mongoose 自带的便利功能自动加createdAt和updatedAt比手动维护 logindate 更省事。Schema Types 内置类型包括 String、Number、Boolean、Array、Buffer、Date、ObjectId、Mixed。Mixed 表示任意类型但修改后需要手动markModified新手尽量少用。ObjectId 用于关联其他集合后面做 populate 时会用到。Model 是由 Schema 生成的构造函数。mongoose.model(User, UserSchema)返回的就是 Model它负责所有数据库操作。注意第一个参数 User 会被 Mongoose 转成集合名 users如果你想要别的集合名就在 Schema 的 options 里显式写collection。到这里连接、Schema、Model 三件套就齐了。目录结构建议是project/ ├── db.js ├── models/ │ └── user.js ├── services/ │ └── userService.js ├── test.js ├── .env └── package.json把数据库操作封装到services/userService.jstest.js只负责调用和打印结果。这样后面写业务逻辑时不会把测试代码和正式代码混在一起。4. 逐条验证 CRUD插入、更新、删除、条件查询与分页的实测结果现在写test.js逐条验证。先插入// test.js const User require(./models/user.js); async function insert() { const user new User({ username: Tracy McGrady, userpwd: abcd1234, userage: 37, email: tracyexample.com, }); try { const res await user.save(); console.log(插入成功:, res); } catch (err) { console.log(插入失败:, err.message); } } insert();执行node test.js控制台会打印出完整文档包含_id、username、createdAt、updatedAt。如果你故意把userpwd写成abc会触发minlength校验报错信息是「密码至少6位」。这就是 Schema 校验的价值不用在业务代码里写一堆if。更新用updateOne或findByIdAndUpdate。前者返回更新结果统计后者返回更新后的文档async function update() { const wherestr { username: Tracy McGrady }; const updatestr { userpwd: zzzz1234 }; const res await User.updateOne(wherestr, updatestr); console.log(更新结果:, res); // { acknowledged: true, modifiedCount: 1, ... } } async function findByIdAndUpdate() { const id 你的实际_id; const res await User.findByIdAndUpdate(id, { userpwd: abcd5678 }, { new: true }); console.log(更新后文档:, res); }注意{ new: true }不加的话返回的是更新前的文档这个坑很多人踩过。updateOne不会触发 Schema 的required校验只有save()和findOneAndUpdate配合runValidators: true才会。所以如果你依赖校验用save()或者显式加runValidators。删除用deleteOne或findByIdAndRemoveasync function del() { const wherestr { username: Tracy McGrady }; const res await User.deleteOne(wherestr); console.log(删除结果:, res); // { acknowledged: true, deletedCount: 1 } }条件查询是重点。find支持字段投影、比较操作符、逻辑操作符async function getByConditions() { // 只返回 username不返回 _id const opt { username: 1, _id: 0 }; const res await User.find({ userage: { $gte: 21, $lte: 65 } }, opt); console.log(年龄21-65的用户:, res); } async function getByRegex() { // 模糊查询用户名含 m不区分大小写 const res await User.find({ username: { $regex: /m/i } }); console.log(模糊查询结果:, res); }常用操作符对照表操作符含义示例$gt大于{ userage: { $gt: 18 } }$gte大于等于{ userage: { $gte: 18 } }$lt小于{ userage: { $lt: 60 } }$lte小于等于{ userage: { $lte: 60 } }$ne不等于{ username: { $ne: admin } }$in在范围内{ userage: { $in: [20, 30, 40] } }$nin不在范围内{ userage: { $nin: [20, 30] } }$or或关系{ $or: [{ userage: 20 }, { username: x }] }$exists字段存在{ email: { $exists: true } }数量查询用countDocuments注意 Mongoose 7 已经废弃了count()async function getCount() { const res await User.countDocuments({ userage: { $gte: 18 } }); console.log(成年用户数量:, res); }分页查询是实际项目里最常用的组合async function getByPager() { const pageSize 5; const currentPage 1; const sort { logindate: -1 }; const condition {}; const skipnum (currentPage - 1) * pageSize; const res await User.find(condition) .skip(skipnum) .limit(pageSize) .sort(sort) .exec(); console.log(第1页数据:, res); }skip在数据量大时性能会下降因为 MongoDB 要扫描并跳过前面的文档。数据量超过十万级时建议用_id或时间戳做游标分页比如find({ _id: { $lt: lastId } }).limit(10)。根据_id查询用findByIdasync function getById() { const id 你的实际_id; const res await User.findById(id); console.log(按ID查询:, res); }所有验证跑完后你会得到一份完整的 CRUD 清单。建议把每个函数单独执行一次观察控制台输出和 MongoDB 里的实际数据。用mongosh或者 Robo 3T 连上去看确认users集合里的文档结构和 Schema 一致。5. 常见报错排查401、local proxy failed、reading choices 与 OAuth 的对照处理这一节按真实报错来。你在接入 TaoToken 和 Mongoose 的过程中大概率会遇到下面几类错误。第一类401 Unauthorized或invalid api key。这个通常出现在调用 TaoToken API 时。检查三件事.env里的TAOTOKEN_API_KEY是否以sk-开头且没有多余空格TAOTOKEN_BASE_URL是否写成https://taotoken.net/api而不是带 UTM 的完整链接请求头里是否带了Authorization: Bearer sk-xxx。如果你用 Claude Code检查settings.json里的ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL是否配对。三件套缺一个就是 401。第二类local proxy failed或connect ECONNREFUSED 127.0.0.1:7890。这个报错说明你的 HTTP 客户端在尝试走本地代理端口但那个端口没有服务在监听。常见原因是环境变量里残留了HTTP_PROXY或HTTPS_PROXY。在 Node 项目里执行console.log(process.env.HTTP_PROXY)确认如果有值在.env里清空或者用delete process.env.HTTP_PROXY。注意不要在任何配置里写代理地址TaoToken 的 API 通道直接访问即可。第三类Cannot read properties of undefined (reading choices)。这个报错出现在解析模型响应时说明返回体结构和你预期的不一样。通常是 Base URL 填错了比如填成了https://taotoken.net而不是https://taotoken.net/api导致请求打到了网页而不是 API 端点。检查你的baseURL配置确保路径以/api结尾。另外确认 Model ID 是有效的无效的 Model ID 有时会返回错误结构而不是标准响应。第四类OAuth error或token exchange failed。如果你用 Codex 的auth.json里面需要包含正确的base_url和api_key。OAuth 流程失败通常是回调地址不匹配或者 Key 权限不足。在 TaoToken 控制台重新生成一个 Key确认它有对应模型的调用权限。auth.json的路径要放对Codex 默认读~/.codex/auth.json放错位置会走默认的 OAuth 流程然后失败。第五类Mongoose 侧的MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017。这是 MongoDB 没启动或者端口不对。Windows 下net start mongodbMac 下brew services list看状态Docker 下docker ps看容器是否在跑。如果 MongoDB 在远程检查连接串里的 IP 和端口以及防火墙规则。第六类ValidationError: userpwd: Path userpwd is required。这是 Schema 校验失败说明你插入的文档缺少必填字段。检查new User({...})里是否漏了字段或者字段名拼写是否和 Schema 一致。Mongoose 严格区分大小写userPwd和userpwd是两个不同的字段。第七类CastError: Cast to Number failed for value abc at path userage。类型转换失败说明你给 Number 类型的字段传了非数字字符串。Mongoose 会尝试转换转不了就报 CastError。检查数据来源必要时在业务层先做类型校验。排障的通用思路是先看报错关键词定位是连接层、认证层还是数据层再用最小可复现脚本单独测试那一层最后对照配置三件套Base URL、Key、Model ID 或连接串逐项核对。不要一上来就改代码先把配置对齐。6. 把凭证收口到 TaoTokenMongoose 项目里的长期维护姿势Mongoose 的 CRUD 跑通之后项目会逐渐长大。你会加用户认证、日志、数据同步、模型调用。这时候如果 Key 还是散落在各个文件里维护成本会指数上升。我的做法是把所有外部调用凭证收口到 TaoToken 的 API 通道Node 项目里只保留一个config/taotoken.js作为唯一出口。具体做法是在services/下建一个aiService.js所有需要调用模型的逻辑都走这个文件它从config/taotoken.js读配置。Mongoose 的 Model 只负责数据持久化不直接碰 API Key。这样职责清晰换 Key 只改.env一行。如果你需要长期跑编码任务或者 Agent 类的自动化流程可以了解 TaoToken 的 Coding Plan它适合需要持续调用、按量计费的场景。如果只是验证某个模型的效果用模型对话入口快速试一下就行。接入文档里有完整的参数说明和示例代码遇到不确定的字段先去文档里查比在代码里猜要快。最后给一个实用建议在package.json里加一个check:config脚本启动前先验证环境变量是否齐全{ scripts: { check:config: node -e \require(./config/taotoken.js); console.log(配置检查通过)\ } }每次部署前跑一次能提前发现 Key 缺失或 Base URL 写错的问题。Mongoose 连接串也建议做同样的检查避免上线后才发现连不上数据库。到这里你已经完成了从 node-mongodb-native 到 Mongoose 的过渡Schema 建模、CRUD 验证、报错排查、凭证收口都走了一遍。下一步可以研究 populate 做集合关联或者用 Mongoose 的中间件在save前后加钩子。把这篇里的test.js留着后面加新功能时继续往里补验证用例比重新搭环境省事得多。
返回列表