1. 从零搭一个 Node.js 接口服务,为什么绕不开 Mongodb 和统一 Key
如果你正在学 Node.js 后端,大概率会遇到这样一个场景:前端页面写好了,数据却没地方存,接口也没地方暴露。这时候 Mongodb + Mongoose + Express 就是一套很顺手的组合。Mongodb 负责存数据,Mongoose 负责用 JS 对象的方式操作数据,Express 负责把数据通过 REST 接口暴露出去。这套链路跑通之后,你就能自己写增删改查接口,不再依赖 json-server 那种临时工具。
但实际开发中还有第二个问题:接口里如果要用到大模型能力,比如智能回复、内容摘要、代码补全,Key 的管理就会变得很麻烦。每个项目一套 Key,环境变量散落在各处,换一个模型就要改一次配置。我试过把模型调用的 endpoint 和鉴权统一收口到 TaoToken,用一套 Key 走同一个 API 通道,Node.js 接口里只需要改 Base URL 和 Model ID 两个地方,维护成本会低很多。
这篇文章会带你从零搭一个歌曲管理接口,包含 Mongoose Schema、连接配置、REST 路由,以及如何把模型调用链路接到 TaoToken。每一步都有可复制的代码和 curl 验证命令,照着做就能跑通。
2. 环境准备与 TaoToken 统一 Key 配置
2.1 安装依赖与目录结构
先建一个空目录,初始化项目:
mkdir node-mongo-api && cd node-mongo-api npm init -y npm i express mongoose dotenv目录结构建议这样组织,后面加路由和模型都不会乱:
node-mongo-api/ ├── .env ├── app.js ├── db.js ├── models/ │ └── song.js ├── routes/ │ └── song.js └── services/ └── llm.jsMongodb 本地服务需要先启动。如果你用的是 zip 版,进入 bin 目录执行:
mongod --dbpath C:\data\db看到waiting for connections就说明服务起来了。连接字符串默认是mongodb://127.0.0.1:27017/,后面跟数据库名。
2.2 TaoToken 是什么,适合谁用
TaoToken 是一个模型调用的统一入口,你可以把它理解成一个 API 网关:不管你后面接的是哪个模型,Node.js 代码里只需要认一个 Base URL 和一把 Key。对于后端接口开发来说,好处是模型调用的配置和业务代码解耦,换模型不用动路由逻辑。
它适合这几类人:正在写 Node.js 接口需要加 AI 能力的后端开发者;手里有多个项目、Key 管理混乱的团队;想用一套配置同时跑对话和编码场景的独立开发者。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api ,注意 API 地址后面不加 UTM 参数。
2.3 把 Key 写进环境变量
在项目根目录建.env文件,把模型调用的配置集中放这里:
# .env PORT=3000 MONGO_URI=mongodb://127.0.0.1:27017/musicdb TAOTOKEN_API_KEY=sk-你的Key TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_MODEL=你的模型ID这里有个坑要注意:.env一定要加进.gitignore,别把 Key 提交到仓库。Key 的获取在控制台里操作,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,进去之后在 API Keys 页面新建一把,复制出来填到.env里就行。
2.4 数据库连接文件 db.js
把 Mongoose 连接单独抽出来,方便后面复用:
// db.js const mongoose = require('mongoose'); async function connectDB() { try { await mongoose.connect(process.env.MONGO_URI); console.log('Mongodb 连接成功'); } catch (err) { console.error('Mongodb 连接失败:', err.message); process.exit(1); } } module.exports = connectDB;Mongoose 8 之后connect返回 Promise,直接用 async/await 就行,不用再写mongoose.connection.on('open')那种回调。连接成功后再启动 Express,顺序不能反,否则接口进来时数据库还没连上。
3. Mongoose Schema 与 REST 接口可复制配置
3.1 定义 Song 模型
Mongoose 的核心是 Schema,它规定了文档的结构和字段类型。新建models/song.js:
// models/song.js const mongoose = require('mongoose'); const songSchema = new mongoose.Schema( { title: { type: String, required: [true, '歌曲名不能为空'], trim: true, }, singer: { type: String, default: '未知歌手', }, price: { type: Number, min: [0, '价格不能为负'], }, genre: { type: String, enum: ['流行', '摇滚', '民谣', '电子'], }, hot: { type: Number, default: 0, }, }, { timestamps: true } ); module.exports = mongoose.model('Song', songSchema);几个字段验证的细节:required后面可以跟数组,第二个元素是自定义错误信息;enum限制取值只能是数组里的;timestamps: true会自动加createdAt和updatedAt,省得自己维护时间字段。
3.2 写 REST 路由
新建routes/song.js,把增删改查都放进去:
// routes/song.js const express = require('express'); const router = express.Router(); const Song = require('../models/song'); // 新增 router.post('/', async (req, res) => { try { const song = await Song.create(req.body); res.status(201).json(song); } catch (err) { res.status(400).json({ error: err.message }); } }); // 查询列表,支持分页和排序 router.get('/', async (req, res) => { const { page = 1, limit = 10, sort = '-hot' } = req.query; const list = await Song.find() .sort(sort) .skip((page - 1) * limit) .limit(Number(limit)); res.json(list); }); // 查询单个 router.get('/:id', async (req, res) => { const song = await Song.findById(req.params.id); if (!song) return res.status(404).json({ error: '歌曲不存在' }); res.json(song); }); // 更新 router.put('/:id', async (req, res) => { const song = await Song.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true, }); if (!song) return res.status(404).json({ error: '歌曲不存在' }); res.json(song); }); // 删除 router.delete('/:id', async (req, res) => { const song = await Song.findByIdAndDelete(req.params.id); if (!song) return res.status(404).json({ error: '歌曲不存在' }); res.json({ message: '删除成功' }); }); module.exports = router;findByIdAndUpdate的new: true很关键,不加的话返回的是更新前的旧文档,容易让人以为没更新成功。runValidators: true保证更新时也走 Schema 验证。
3.3 模型调用服务 llm.js
把 TaoToken 的调用封装成独立服务,路由里只调函数,不关心底层用哪个模型:
// services/llm.js const API_KEY = process.env.TAOTOKEN_API_KEY; const BASE_URL = process.env.TAOTOKEN_BASE_URL; const MODEL = process.env.TAOTOKEN_MODEL; async function chat(prompt) { const resp = await fetch(`${BASE_URL}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_KEY}`, }, body: JSON.stringify({ model: MODEL, messages: [{ role: 'user', content: prompt }], }), }); if (!resp.ok) { const text = await resp.text(); throw new Error(`模型调用失败 ${resp.status}: ${text}`); } const data = await resp.json(); return data.choices[0].message.content; } module.exports = { chat };这里三件套要写全:Base URL 是https://taotoken.net/api,Key 从环境变量读,Model ID 也放环境变量。三个值缺一个都会报错,后面排障章节会细说。
3.4 组装 app.js
// app.js require('dotenv').config(); const express = require('express'); const connectDB = require('./db'); const songRouter = require('./routes/song'); const { chat } = require('./services/llm'); const app = express(); app.use(express.json()); app.use('/api/songs', songRouter); // 模型调用测试接口 app.post('/api/ai/chat', async (req, res) => { try { const reply = await chat(req.body.prompt || '你好'); res.json({ reply }); } catch (err) { res.status(500).json({ error: err.message }); } }); const PORT = process.env.PORT || 3000; connectDB().then(() => { app.listen(PORT, () => console.log(`服务已启动: http://localhost:${PORT}`)); });启动命令:
node app.js看到Mongodb 连接成功和服务已启动两行日志,说明链路通了。
4. 用 curl 验证增删改查与鉴权是否生效
4.1 新增一条歌曲
curl -X POST http://localhost:3000/api/songs \ -H "Content-Type: application/json" \ -d '{"title":"干杯","singer":"五月天","price":3,"genre":"流行","hot":95}'返回 201 和带_id的文档,说明写入成功。_id是 Mongodb 自动生成的,后面查询和删除都要用它。
4.2 查询列表
curl "http://localhost:3000/api/songs?page=1&limit=5&sort=-hot"返回一个数组,按热度倒序。如果返回空数组,先确认数据库名和连接字符串是否一致,Mongodb 不会报错,只会默默给你一个空集合。
4.3 更新与删除
curl -X PUT http://localhost:3000/api/songs/你的ID \ -H "Content-Type: application/json" \ -d '{"price":5}' curl -X DELETE http://localhost:3000/api/songs/你的ID更新返回的文档里price应该变成 5,删除返回{"message":"删除成功"}。
4.4 验证 TaoToken 鉴权
curl -X POST http://localhost:3000/api/ai/chat \ -H "Content-Type: application/json" \ -d '{"prompt":"用一句话介绍 Node.js"}'如果返回{"reply":"..."},说明 Key 和 Base URL 都生效了。如果返回 401,先检查.env里的 Key 有没有多余空格,再确认Authorization头是不是Bearer开头。模型对话的在线调试入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,可以先去那里确认 Key 本身可用。
5. 常见报错排查:401、local proxy failed、reading choices
5.1 401 Unauthorized
这是最常见的鉴权错误。原因通常有三个:Key 复制时带了换行或空格;.env没被dotenv加载,检查require('dotenv').config()是不是在文件最顶部;请求头拼写错误,正确的是Authorization: Bearer sk-xxx,Bearer 和 Key 之间有一个空格。
排查动作:在services/llm.js里临时打印API_KEY的前 8 位和后 4 位,确认读到的值和你控制台里的一致。如果打印出来是undefined,说明环境变量没加载成功。
5.2 local proxy failed
这个报错一般出现在你本地网络环境有额外代理设置的时候。Node.js 的 fetch 会读取系统代理,如果代理配置指向了一个不可用的地址,请求就会失败。解决办法是在启动命令前清掉代理环境变量:
# Windows PowerShell $env:HTTP_PROXY=""; $env:HTTPS_PROXY=""; node app.js # macOS / Linux unset HTTP_PROXY HTTPS_PROXY && node app.js或者在代码里显式指定不走代理,但更推荐从环境层面解决,避免影响其他请求。
5.3 Cannot read properties of undefined (reading 'choices')
这个报错说明data.choices是 undefined,也就是响应体结构和你预期的不一样。常见原因是 Base URL 写错了,比如写成了https://taotoken.net而漏了/api,或者多写了一个/v1导致路径变成/api/v1/v1/chat/completions。
排查动作:在services/llm.js里把原始响应打出来:
const data = await resp.json(); console.log('原始响应:', JSON.stringify(data).slice(0, 200));看到实际返回结构后再调整取值路径。正常情况下choices[0].message.content就是回复文本。
5.4 Mongoose 连接超时
如果启动时卡在Mongodb 连接失败,先确认mongod进程在跑。Windows 下可以打开任务管理器看有没有mongod.exe。另一个常见原因是MONGO_URI里的数据库名带了特殊字符,换成纯英文小写最稳妥。
5.5 三件套对照表
| 配置项 | 正确值 | 常见错误 |
|---|---|---|
| Base URL | https://taotoken.net/api | 漏 /api 或多 /v1 |
| API Key | sk- 开头完整字符串 | 带空格或换行 |
| Model ID | 控制台里复制的完整 ID | 手写拼错或大小写不符 |
这三个值任何一个不对,都会导致调用失败。建议统一放.env,代码里只读环境变量,不硬编码。
6. 把配置收口到一处,后面换模型不用改路由
整套跑下来,你会发现真正需要改的地方只有.env一个文件。路由、模型、数据库连接都是稳定的,模型调用被封装在services/llm.js里,换模型只改TAOTOKEN_MODEL这一行。这种结构在项目变大之后优势很明显:业务代码和外部依赖解耦,测试的时候也容易 mock。
如果你后面要长期跑编码类任务或者 Agent 场景,可以了解下 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它更适合高频调用的场景。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有各语言的完整示例。API Keys 管理页面是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,Key 的创建和轮换都在那里操作。
最后留一个实用技巧:在app.js里加一个健康检查接口,部署后第一时间能确认服务状态。
app.get('/health', (req, res) => { res.json({ status: 'ok', db: mongoose.connection.readyState === 1 ? 'connected' : 'disconnected', }); });readyState为 1 表示数据库连接正常,0 表示断开。这个接口不依赖任何业务逻辑,排查问题时先打它,能快速定位是服务挂了还是数据库断了。