多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

MongoDB与JavaScript全栈开发实战指南

MongoDB与JavaScript全栈开发实战指南 1. MongoDB与JavaScript全栈开发实战指南作为现代Web开发的核心技术栈JavaScript与MongoDB的组合正在重塑数据存储与处理的范式。我至今记得第一次用Node.js直连MongoDB时那种摆脱ORM束缚的自由感——文档型数据库与动态语言的天然契合让全栈开发变得前所未有的流畅。本文将带你深入这个技术组合的实战应用从基础连接到高级查询全是真枪实弹的代码示例。2. 环境配置与基础连接2.1 MongoDB安装与验证Windows平台推荐使用官方MSI安装包当前稳定版6.0安装时注意勾选Install MongoDB as a Service选项。安装完成后在PowerShell运行mongod --version看到版本输出即表示安装成功。Linux用户更简单sudo apt-get install -y mongodb-org systemctl start mongod2.2 Node.js驱动选择主流有三个选择官方mongodb驱动最轻量Mongoose含Schema验证TypeORMTypeScript首选新手建议从官方驱动开始npm install mongodb2.3 连接字符串安全实践永远不要把连接字符串硬编码在代码里正确的做法是// config.js module.exports { db: { url: process.env.DB_URL || mongodb://localhost:27017, name: myapp_ (process.env.NODE_ENV || development) } }然后在主文件const { MongoClient } require(mongodb); const config require(./config); (async () { const client new MongoClient(config.db.url); try { await client.connect(); console.log(Connected to MongoDB); } catch (err) { console.error(Connection error:, err.stack); } })();3. CRUD操作深度解析3.1 文档插入的三种方式const db client.db(config.db.name); const users db.collection(users); // 单条插入 const insertOneResult await users.insertOne({ username: dev_user, roles: [developer], meta: { createdAt: new Date(), active: true } }); // 批量插入 const insertManyResult await users.insertMany([ { username: user1, type: free }, { username: user2, type: pro } ]); // 插入时自动生成_id const withCustomId await users.insertOne({ _id: new ObjectId(), // 显式声明 username: admin });3.2 查询构建技巧MongoDB的查询语法极其灵活// 基础查询 const activeUsers await users.find({ meta.active: true }).toArray(); // 分页查询重要 const paginated await users.find() .sort({ createdAt: -1 }) // 最新优先 .skip((pageNum - 1) * pageSize) .limit(pageSize) .project({ username: 1, type: 1 }); // 只返回指定字段 // 聚合查询示例 const userStats await users.aggregate([ { $match: { type: { $exists: true } } }, { $group: { _id: $type, count: { $sum: 1 }, lastActive: { $max: $meta.createdAt } }} ]).toArray();3.3 更新操作陷阱规避新手常犯的错误是直接替换文档// 错误做法会丢失其他字段 await users.updateOne( { _id: targetId }, { username: new_name } ); // 正确做法 await users.updateOne( { _id: targetId }, { $set: { username: new_name } } ); // 带条件的更新 await users.updateMany( { type: free, lastLogin: { $lt: new Date(Date.now() - 30*24*60*60*1000) } }, { $set: { status: inactive } } );4. 高级特性实战4.1 事务处理MongoDB 4.0支持多文档事务const session client.startSession(); try { await session.withTransaction(async () { const accountCollection db.collection(accounts); await accountCollection.updateOne( { _id: account1 }, { $inc: { balance: -100 } }, { session } ); await accountCollection.updateOne( { _id: account2 }, { $inc: { balance: 100 } }, { session } ); }); } finally { await session.endSession(); }4.2 索引优化策略// 创建组合索引 await users.createIndex({ username: 1, meta.createdAt: -1 }); // 文本搜索索引 await posts.createIndex({ content: text }); const searchResults await posts.find({ $text: { $search: javascript mongodb } }).toArray(); // 查看索引使用情况 const explainResult await users.find( { username: test } ).explain(executionStats);4.3 Change Stream实时监听const changeStream users.watch([ { $match: { operationType: insert } } ]); changeStream.on(change, (change) { console.log(New user:, change.fullDocument); // 可以触发邮件通知、更新缓存等操作 });5. 性能优化与错误处理5.1 连接池配置const client new MongoClient(config.db.url, { poolSize: 50, // 默认5 connectTimeoutMS: 5000, socketTimeoutMS: 30000, retryWrites: true });5.2 常见错误处理模式try { await someDbOperation(); } catch (err) { if (err instanceof MongoServerError) { switch(err.code) { case 11000: // 重复键 console.error(Duplicate key error); break; case 121: // 文档验证失败 console.error(Document validation failed); break; default: throw err; } } else { throw err; } }5.3 批量操作最佳实践const bulkOps users.initializeUnorderedBulkOp(); for (let i 0; i 1000; i) { bulkOps.insert({ username: user_${i}, index: i }); } const result await bulkOps.execute(); console.log(Inserted ${result.nInserted} documents);6. 与Express整合实战6.1 中间件封装// dbMiddleware.js const { MongoClient } require(mongodb); const config require(./config); let cachedDb null; async function connectToDatabase() { if (cachedDb) return cachedDb; const client await MongoClient.connect(config.db.url); const db client.db(config.db.name); cachedDb { db, client }; return cachedDb; } module.exports async (req, res, next) { try { const { db, client } await connectToDatabase(); req.db db; req.mongoClient client; next(); } catch (err) { next(err); } };6.2 RESTful API示例const express require(express); const dbMiddleware require(./dbMiddleware); const app express(); app.use(express.json()); app.use(dbMiddleware); // 用户列表 app.get(/users, async (req, res) { const users await req.db.collection(users) .find() .project({ password: 0 }) .toArray(); res.json(users); }); // 创建用户 app.post(/users, async (req, res) { const { username, email } req.body; if (!username || !email) { return res.status(400).json({ error: Missing fields }); } try { const result await req.db.collection(users).insertOne({ username, email, createdAt: new Date() }); res.status(201).json(result.ops[0]); } catch (err) { if (err.code 11000) { res.status(409).json({ error: User already exists }); } else { res.status(500).json({ error: Database error }); } } });7. 安全防护措施7.1 注入防御永远不要这样拼接查询// 危险代码 const query {username: ${req.params.username}}; const user await db.collection(users).findOne(JSON.parse(query));应该使用驱动程序的查询构造器const user await db.collection(users).findOne({ username: req.params.username });7.2 字段过滤防止意外返回敏感字段// 错误做法 const user await users.findOne({ _id: userId }); // 正确做法 const user await users.findOne( { _id: userId }, { projection: { password: 0, token: 0 } } );7.3 角色权限控制在MongoDB 4.2中可以使用自定义角色use admin db.createRole({ role: appReader, privileges: [{ resource: { db: myapp, collection: }, actions: [find, aggregate] }], roles: [] }) db.createUser({ user: readonly_user, pwd: securepassword, roles: [appReader] })8. 调试与性能分析8.1 查询分析器// 开启分析 await db.setProfilingLevel(2); // 获取慢查询 const slowOps await db.collection(system.profile) .find({ millis: { $gt: 100 } }) .sort({ ts: -1 }) .toArray();8.2 Explain()使用技巧const explanation await users.find({ createdAt: { $gt: new Date(2023-01-01) } }).explain(executionStats); console.log(explanation.executionStats); // 关注 // - totalKeysExamined // - totalDocsExamined // - executionTimeMillis8.3 客户端监控使用MongoDB Atlas免费版提供的性能监控工具或自建PrometheusGranfana监控# docker-compose.yml 片段 monitoring: image: prom/prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml9. 项目结构建议专业项目应该这样组织project/ ├── config/ │ ├── default.json │ └── production.json ├── models/ │ ├── User.js │ └── Product.js ├── services/ │ ├── database.js │ └── cache.js ├── migrations/ │ └── 20230501-add-indexes.js ├── scripts/ │ └── seed-db.js └── app.js其中services/database.js示例const { MongoClient } require(mongodb); const config require(config); let client null; let db null; async function init() { client new MongoClient(config.get(db.url), { ignoreUndefined: true // 允许存储undefined字段 }); await client.connect(); db client.db(config.get(db.name)); } function getCollection(name) { if (!db) throw new Error(Database not initialized); return db.collection(name); } module.exports { init, getCollection };10. 迁移与部署10.1 数据库迁移使用mongodump/mongorestore# 导出 mongodump --urimongodb://source-host:27017 --out./backup # 导入 mongorestore --urimongodb://target-host:27017 ./backup10.2 Docker部署FROM node:18 WORKDIR /app COPY package*.json ./ RUN npm install --production COPY . . # 使用wait-for-it等待MongoDB就绪 ADD https://github.com/vishnubob/wait-for-it/raw/master/wait-for-it.sh /wait-for-it.sh RUN chmod x /wait-for-it.sh CMD [/wait-for-it.sh, db:27017, --, node, app.js]对应的docker-compose.ymlversion: 3 services: app: build: . ports: - 3000:3000 depends_on: - db environment: - DB_URLmongodb://db:27017 db: image: mongo:6.0 volumes: - db-data:/data/db ports: - 27017:27017 volumes: db-data:11. 实际项目经验分享在电商项目中我们这样设计商品分类// 支持无限级分类 const categories db.collection(categories); await categories.insertMany([ { _id: electronics, name: Electronics, path: electronics, level: 1 }, { _id: phones, name: Phones, parent: electronics, path: electronics.phones, level: 2 } ]); // 查询某个分类的所有子分类 async function getSubcategories(categoryId) { const category await categories.findOne({ _id: categoryId }); if (!category) return []; return categories.find({ path: new RegExp(^${category.path}\\.) }).toArray(); }性能优化案例有个查询原本需要200ms添加以下索引后降到5msawait orders.createIndex({ userId: 1, status: 1, createdAt: -1 });12. 常见问题解决方案连接超时问题检查防火墙设置后尝试在连接字符串添加参数mongodb://host:27017/?connectTimeoutMS3000socketTimeoutMS30000游标内存不足处理大量数据时使用批处理const batchSize 100; const cursor users.find().batchSize(batchSize); while (await cursor.hasNext()) { const batch []; for (let i 0; i batchSize await cursor.hasNext(); i) { batch.push(await cursor.next()); } processBatch(batch); }地理空间查询示例await places.createIndex({ location: 2dsphere }); const nearby await places.find({ location: { $near: { $geometry: { type: Point, coordinates: [longitude, latitude] }, $maxDistance: 5000 // 5公里内 } } }).toArray();13. 最新特性展望MongoDB 6.0值得关注的新功能时序集合专为时间序列数据优化db.createCollection(sensor_data, { timeseries: { timeField: timestamp, metaField: sensorId, granularity: hours } });聚合管道增强db.sales.aggregate([ { $setWindowFields: { sortBy: { date: 1 }, output: { movingAvg: { $avg: $amount, window: { documents: [unbounded, current] } } } }} ]);客户端字段级加密const client new MongoClient(uri, { autoEncryption: { keyVaultNamespace: encryption.__keyVault, kmsProviders: { local: { key: masterKey } }, extraOptions: { cryptSharedLibPath: /path/to/mongo_crypt_v1.so } } });14. 学习资源推荐官方文档重点章节MongoDB Node Driver DocsAggregation PipelineTransactions实战项目建议构建一个博客系统实现文章-评论嵌套关系按标签分类查询全文搜索功能开发实时分析看板使用Change Stream监听数据变化聚合管道生成统计指标WebSocket推送实时更新调试工具MongoDB Compass官方GUIRobo 3T轻量级客户端VS Code的MongoDB插件
返回列表