1. 从零跑通 MongoDB + Mongoose 的最小闭环
如果你刚开始写 Node.js 后端,大概率会遇到这样一个场景:接口写完了,数据却不知道往哪存。用 JSON 文件吧,并发一上来就乱;上 MySQL 吧,建表、改字段、写 SQL,对刚入门的人来说心智负担不小。这时候 MongoDB 就成了一个很顺手的过渡选择——它存的是文档,长得就像 JavaScript 对象,你几乎不用切换思维就能把数据塞进去。
MongoDB 是一个面向文档的 NoSQL 数据库,一条记录叫一个 document,结构类似 JSON。和 MySQL 那种「先建表、定字段、再插入」的流程不同,MongoDB 的集合(collection)是模式自由的,字段可以随时增减。这对快速迭代的项目特别友好,比如内容管理、博客、事件日志这类场景。
但直接裸用官方驱动写起来还是有点啰嗦,所以社区里更常见的是用 Mongoose。Mongoose 可以理解成 MongoDB 的「对象建模层」,类似 jQuery 之于原生 JS:它帮你把连接管理、Schema 校验、链式查询都封装好了。你定义好 Schema,它就能在写入前帮你校验类型、补默认值,读出来还能直接当对象用。
这篇就带你从本地环境开始,用 Mongoose 连上 MongoDB,把增删改查跑成一个最小闭环。同时我会把模型调用所需的 endpoint 统一改到 TaoToken,这样你后面接大模型能力时,Key 和地址都是同一套,不用来回切配置。适合谁?刚接触 Node.js 后端、想快速把数据存起来、又不想被 SQL 语法劝退的开发者。
2. 环境准备与 TaoToken 统一 Key 前置
在写代码之前,先把两件事准备好:本地 MongoDB 服务,以及一个能统一管理模型调用的 Key。
MongoDB 的安装方式看你系统。macOS 用 Homebrew 最省事,brew tap mongodb/brew && brew install mongodb-community,然后brew services start mongodb-community就能跑起来。Windows 用户去官网下 msi 安装包,安装时勾选「Install MongoDB as a Service」,装完服务会自动启动。Linux 就按发行版走 apt 或 yum。装完后默认监听127.0.0.1:27017,没有用户名密码,本地开发够用了。
验证服务是否起来,终端敲一句:
mongosh --eval "db.runCommand({ ping: 1 })"返回{ ok: 1 }就说明数据库在跑。如果提示 command not found,说明 mongosh 没装,可以单独装一下,或者用老版的mongo命令。
接下来是 TaoToken 的部分。为什么这里要提它?因为很多同学在本地写 CRUD 写得好好的,一旦要接模型能力(比如给插入的数据做摘要、做分类),就得再去申请一家家的 Key,配置散落在各处。TaoToken 的思路是把模型服务的入口统一成一个 endpoint,你只需要维护一个 Key,后面换模型、加模型都只改一个地方。
先去官网注册并拿到 Key:
# 打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= # 注册后在控制台创建 API Key拿到 Key 之后,把它写进项目根目录的.env文件,别硬编码在代码里:
# .env MONGO_URL=mongodb://127.0.0.1:27017/datadb TAOTOKEN_API_KEY=sk-你的key TAOTOKEN_BASE_URL=https://taotoken.net/api这里TAOTOKEN_BASE_URL就是统一的模型服务入口,后面不管你是用 OpenAI 兼容的 SDK,还是直接发 HTTP 请求,Base URL 都填这个。Key 的管理页面在 https://taotoken.net/api-keys ,需要轮换或新建的时候去那里操作。
项目初始化:
mkdir mongo-mongoose-demo && cd mongo-mongoose-demo npm init -y npm install mongoose dotenvdotenv用来加载.env,mongoose就是主角。装完在package.json里加一句"type": "module",后面用 ESM 写法更清爽。
3. 可复制的连接配置与 Schema 定义
这一步是核心,我把连接、Schema、Model 三块拆开写,你可以直接复制。
先建db.js,专门管连接:
// db.js import mongoose from 'mongoose'; import dotenv from 'dotenv'; dotenv.config(); const MONGO_URL = process.env.MONGO_URL || 'mongodb://127.0.0.1:27017/datadb'; export async function connectDB() { try { await mongoose.connect(MONGO_URL, { serverSelectionTimeoutMS: 5000, }); console.log('Mongoose connection open to ' + MONGO_URL); } catch (err) { console.log('Mongoose connection error: ' + err); process.exit(1); } } mongoose.connection.on('disconnected', () => { console.log('Mongoose connection disconnected'); }); export default mongoose;注意serverSelectionTimeoutMS这个参数,默认是 30 秒,本地连不上时要等很久才报错。设成 5 秒能让你更快发现问题。另外新版 Mongoose 已经不需要useNewUrlParser了,网上很多老教程还带着这个参数,加上去反而会警告,可以去掉。
然后是 Schema 和 Model,建models/person.js:
// models/person.js import mongoose from '../db.js'; const PersonSchema = new mongoose.Schema({ name: { type: String, required: true }, age: { type: Number, default: 0, min: 0 }, email: { type: String, default: '' }, time: { type: Date, default: Date.now }, }); // 第三个参数显式指定集合名,避免被自动加 s const PersonModel = mongoose.model('Person', PersonSchema, 'people'); export default PersonModel;这里有个坑要提前说:Mongoose 默认会把 Model 名小写并加复数。你写mongoose.model('Person', ...),它实际操作的集合是people。如果你想要精确控制集合名,就像上面那样传第三个参数。我见过不少人查数据查不到,最后发现是集合名对不上。
Schema 里required和min是校验规则,写入不符合的数据会直接抛错,这比裸驱动多了一层保护。default则会在你没传字段时自动补上。
如果你还想在同一个项目里调用模型服务,可以再加一个llm.js,把 TaoToken 的入口封装好:
// llm.js import dotenv from 'dotenv'; dotenv.config(); const BASE_URL = process.env.TAOTOKEN_BASE_URL || 'https://taotoken.net/api'; const API_KEY = process.env.TAOTOKEN_API_KEY; export async function chat(prompt, model = 'gpt-4o-mini') { const res = await fetch(`${BASE_URL}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_KEY}`, }, body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }], }), }); if (!res.ok) { throw new Error(`LLM request failed: ${res.status}`); } const data = await res.json(); return data.choices[0].message.content; }这样数据库和模型服务就是两套独立的配置,但 Key 都从同一个.env读,管理起来不散。
4. 增删改查脚本与链路验证
配置齐了,写一个crud.js把四个操作串起来,跑一遍看结果。
// crud.js import { connectDB } from './db.js'; import PersonModel from './models/person.js'; async function main() { await connectDB(); // 增 const created = await PersonModel.create({ name: '老王', age: 22, email: 'laowang@qq.com', }); console.log('插入成功:', created); // 查(条件查询) const found = await PersonModel.find({ age: { $gt: 18 } }); console.log('查询结果:', found); // 改 const updated = await PersonModel.updateOne( { name: '老王' }, { $set: { age: 100 } } ); console.log('修改结果:', updated); // 删 const deleted = await PersonModel.deleteOne({ name: '老王' }); console.log('删除结果:', deleted); process.exit(0); } main();跑之前确认 MongoDB 服务在跑,然后:
node crud.js正常的话你会看到类似输出:
Mongoose connection open to mongodb://127.0.0.1:27017/datadb 插入成功: { name: '老王', age: 22, email: 'laowang@qq.com', _id: new ObjectId('...'), time: 2024-..., __v: 0 } 查询结果: [ { _id: ..., name: '老王', age: 22, ... } ] 修改结果: { acknowledged: true, modifiedCount: 1, ... } 删除结果: { acknowledged: true, deletedCount: 1 }_id是 MongoDB 自动生成的主键,__v是 Mongoose 的版本字段,用来处理并发,不用管它。
如果你想验证模型调用这条链路,可以在插入后加一句:
import { chat } from './llm.js'; const summary = await chat(`用一句话描述这个人:${created.name},${created.age}岁`); console.log('模型返回:', summary);能打印出模型返回的内容,说明 TaoToken 的 endpoint 和 Key 都通了。想单独测模型对话,可以去 https://taotoken.net/api 的模型对话页面直接试,不用写代码就能验证 Key 是否有效。
这里有个细节:find返回的是数组,findOne返回单个文档或 null。很多人第一次用会混淆,导致后面取属性报 undefined。记住「find 加 s 就是复数」这个口诀就行。
5. 常见报错排查对照
跑不通的时候,对照下面几个真实报错看。
报错一:MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
这是最常见的,意思是连不上数据库。先确认服务在跑:brew services list或systemctl status mongod。如果服务没起,启动它。如果服务起了还报这个,检查.env里的MONGO_URL端口是不是写错了,默认是 27017,别写成 27018。
报错二:MongooseError: Operation 'people.insertOne()' buffering timed out after 10000ms
这个报错说明连接还没建立好,你就开始操作数据库了。Mongoose 默认会缓冲操作,等连接成功再执行,但超时就报这个。解决办法是确保await connectDB()在create之前执行。如果你在模块顶层直接调create,而连接是异步的,就会踩这个坑。
报错三:ValidationError: Person validation failed: name: Path 'name' is required.
这是 Schema 校验拦下来的,说明你插入的数据缺了required字段。检查一下create传的对象里有没有name。这是好事,说明校验生效了,别把它当 bug。
报错四:401 Unauthorized或LLM request failed: 401
这是模型调用那条链路的报错,说明 TaoToken 的 Key 不对或没读到。检查.env里TAOTOKEN_API_KEY有没有写错,dotenv.config()有没有在llm.js顶部调用。如果 Key 是对的还报 401,去 https://taotoken.net/api-keys 确认 Key 是否被禁用或过期。
报错五:reading 'choices'或Cannot read properties of undefined (reading 'choices')
这个通常出现在解析模型返回时。原因可能是接口返回了错误结构,但你没检查res.ok就直接取data.choices。上面llm.js里我加了if (!res.ok) throw,就是为了避免这个。如果你自己写的时候没加,补上。
报错六:local proxy failed或连接超时
如果你在请求模型服务时看到代理相关的报错,先检查环境变量里有没有残留的HTTP_PROXY/HTTPS_PROXY。本地开发一般不需要走代理,清掉这些变量再试。TaoToken 的 endpoint 是直连的,不需要额外代理配置。
报错七:OAuth相关错误
如果你用的是某些 CLI 工具(比如 Claude Code 这类),可能会遇到 OAuth 认证失败。这类工具通常需要你在配置文件里填 Base URL、Key、Model ID 三件套。以 Claude Code 为例,配置在~/.claude/settings.json:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的key", "ANTHROPIC_MODEL": "claude-3-5-sonnet-20241022" } }三件套缺一不可:Base URL 指向 TaoToken 的 API 入口,Key 用你在控制台创建的,Model ID 填你要用的模型。如果只填了 Key 没填 Base URL,它会去连默认地址,自然认证失败。
排查顺序建议是:先确认 MongoDB 服务在跑,再确认连接代码有 await,最后确认模型链路的 Key 和 Base URL。一层层来,别跳步。
6. 把 Key 统一起来,后面少折腾
跑通这个 CRUD 闭环之后,你手里其实已经有了一个可复用的骨架:db.js管数据库连接,models/管数据结构,llm.js管模型调用。后面加功能,无非是加 Model、加接口。
我自己的习惯是把所有外部服务的入口都收敛到.env里,数据库一个 URL,模型服务一个 Base URL 加一个 Key。这样换环境、换模型的时候,只改配置不改代码。TaoToken 在这里的价值就是那个统一的 endpoint——你不用为每个模型单独记地址,Key 也只需要维护一份。
如果你后面要长期写代码、跑 Agent 类的任务,可以看看 Coding Plan,它把常用的编码场景打包好了,省得自己一个个配。地址是 https://taotoken.net/api 里的 coding-plan 入口。需要看接入细节的话,文档在 https://taotoken.net/api 的 doc 页面,里面有各语言的示例。
最后留一个实用技巧:调试 Mongoose 查询时,在连接后加一句mongoose.set('debug', true),它会把实际执行的 MongoDB 命令打印到控制台。查不到数据的时候,看一眼真实查询语句,往往一眼就能发现问题,比猜快得多。