插件窝 干货文章 MongoDB与 Node.js 集成开发实战

MongoDB与 Node.js 集成开发实战

const User mongoose javascript 721    来源:    2025-03-27

MongoDB与Node.js集成开发实战指南

一、环境准备

1. 安装必要软件

  • Node.js: 建议安装LTS版本(16.x或18.x)
  • MongoDB: 安装社区版(4.4+)或使用Atlas云服务
  • MongoDB Compass: 可选的可视化管理工具

2. 初始化Node.js项目

mkdir node-mongo-app
cd node-mongo-app
npm init -y

二、安装依赖

npm install mongodb mongoose express cors dotenv
  • mongodb: 官方MongoDB Node.js驱动
  • mongoose: MongoDB对象建模工具
  • express: Web应用框架
  • cors: 跨域资源共享中间件
  • dotenv: 环境变量管理

三、基础连接配置

1. 创建.env文件

MONGODB_URI=mongodb://localhost:27017/mydatabase
PORT=3000

2. 创建数据库连接文件 (db.js)

const { MongoClient } = require('mongodb');
require('dotenv').config();

let client;
let db;

async function connectToDatabase() {
  if (db) return db;

  try {
    client = new MongoClient(process.env.MONGODB_URI);
    await client.connect();
    db = client.db();
    console.log('Connected to MongoDB');
    return db;
  } catch (err) {
    console.error('MongoDB connection error:', err);
    process.exit(1);
  }
}

async function closeConnection() {
  if (client) {
    await client.close();
    console.log('MongoDB connection closed');
  }
}

module.exports = { connectToDatabase, closeConnection };

四、使用Mongoose进行数据建模

1. 创建模型 (models/User.js)

const mongoose = require('mongoose');
const { Schema } = mongoose;

const userSchema = new Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

// 添加更新时间的钩子
userSchema.pre('save', function(next) {
  this.updatedAt = Date.now();
  next();
});

module.exports = mongoose.model('User', userSchema);

2. 初始化Mongoose连接

const mongoose = require('mongoose');
require('dotenv').config();

async function connectMongoose() {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
    console.log('Mongoose connected to MongoDB');
  } catch (err) {
    console.error('Mongoose connection error:', err);
    process.exit(1);
  }
}

module.exports = connectMongoose;

五、实现CRUD操作

1. 创建服务层 (services/userService.js)

const User = require('../models/User');

class UserService {
  // 创建用户
  static async createUser(userData) {
    try {
      const user = new User(userData);
      await user.save();
      return user;
    } catch (err) {
      throw err;
    }
  }

  // 获取所有用户
  static async getUsers() {
    try {
      return await User.find({});
    } catch (err) {
      throw err;
    }
  }

  // 获取单个用户
  static async getUserById(id) {
    try {
      return await User.findById(id);
    } catch (err) {
      throw err;
    }
  }

  // 更新用户
  static async updateUser(id, updateData) {
    try {
      return await User.findByIdAndUpdate(id, updateData, { new: true });
    } catch (err) {
      throw err;
    }
  }

  // 删除用户
  static async deleteUser(id) {
    try {
      return await User.findByIdAndDelete(id);
    } catch (err) {
      throw err;
    }
  }
}

module.exports = UserService;

2. 创建控制器 (controllers/userController.js)

const UserService = require('../services/userService');

class UserController {
  static async createUser(req, res) {
    try {
      const user = await UserService.createUser(req.body);
      res.status(201).json(user);
    } catch (err) {
      res.status(400).json({ error: err.message });
    }
  }

  static async getUsers(req, res) {
    try {
      const users = await UserService.getUsers();
      res.json(users);
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  }

  static async getUser(req, res) {
    try {
      const user = await UserService.getUserById(req.params.id);
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      res.json(user);
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  }

  static async updateUser(req, res) {
    try {
      const user = await UserService.updateUser(req.params.id, req.body);
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      res.json(user);
    } catch (err) {
      res.status(400).json({ error: err.message });
    }
  }

  static async deleteUser(req, res) {
    try {
      const user = await UserService.deleteUser(req.params.id);
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      res.json({ message: 'User deleted successfully' });
    } catch (err) {
      res.status(500).json({ error: err.message });
    }
  }
}

module.exports = UserController;

六、创建Express应用

1. 主应用文件 (app.js)

const express = require('express');
const cors = require('cors');
const connectMongoose = require('./config/mongoose');
const userRoutes = require('./routes/userRoutes');
require('dotenv').config();

const app = express();

// 中间件
app.use(cors());
app.use(express.json());

// 数据库连接
connectMongoose();

// 路由
app.use('/api/users', userRoutes);

// 健康检查端点
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'OK' });
});

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

2. 创建路由 (routes/userRoutes.js)

const express = require('express');
const UserController = require('../controllers/userController');

const router = express.Router();

router.post('/', UserController.createUser);
router.get('/', UserController.getUsers);
router.get('/:id', UserController.getUser);
router.put('/:id', UserController.updateUser);
router.delete('/:id', UserController.deleteUser);

module.exports = router;

七、高级主题

1. 索引优化

// 在模型定义中添加索引
userSchema.index({ username: 1 }, { unique: true });
userSchema.index({ email: 1 }, { unique: true });
userSchema.index({ createdAt: -1 }); // 按创建时间降序

2. 聚合管道示例

static async getUserStats() {
  try {
    return await User.aggregate([
      {
        $group: {
          _id: {
            year: { $year: "$createdAt" },
            month: { $month: "$createdAt" }
          },
          count: { $sum: 1 }
        }
      },
      { $sort: { "_id.year": 1, "_id.month": 1 } }
    ]);
  } catch (err) {
    throw err;
  }
}

3. 事务处理

static async transferCredits(fromUserId, toUserId, amount) {
  const session = await mongoose.startSession();
  session.startTransaction();

  try {
    const fromUser = await User.findById(fromUserId).session(session);
    if (fromUser.credits < amount) {
      throw new Error('Insufficient credits');
    }

    const toUser = await User.findById(toUserId).session(session);

    fromUser.credits -= amount;
    toUser.credits += amount;

    await fromUser.save({ session });
    await toUser.save({ session });

    await session.commitTransaction();
    session.endSession();

    return { fromUser, toUser };
  } catch (err) {
    await session.abortTransaction();
    session.endSession();
    throw err;
  }
}

八、性能优化技巧

  1. 连接池管理:

    mongoose.connect(process.env.MONGODB_URI, {
     poolSize: 10, // 连接池大小
     socketTimeoutMS: 30000,
     connectTimeoutMS: 30000
    });
    
  2. 批量操作:

    // 批量插入
    await User.insertMany(usersArray);
    
    // 批量更新
    await User.updateMany(
     { status: 'active' },
     { $set: { lastLogin: new Date() } }
    );
    
  3. 查询优化:

    // 只选择需要的字段
    User.find().select('username email');
    
    // 使用游标处理大数据集
    const cursor = User.find().cursor();
    for (let user = await cursor.next(); user != null; user = await cursor.next()) {
     // 处理每个用户
    }
    

九、安全最佳实践

  1. 输入验证:

    const Joi = require('joi');
    
    const userSchema = Joi.object({
     username: Joi.string().alphanum().min(3).max(30).required(),
     email: Joi.string().email().required(),
     password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{8,30}$')).required()
    });
    
  2. 密码哈希:

    const bcrypt = require('bcrypt');
    const saltRounds = 10;
    
    // 保存前哈希密码
    userSchema.pre('save', async function(next) {
     if (!this.isModified('password')) return next();
     this.password = await bcrypt.hash(this.password, saltRounds);
     next();
    });
    
  3. 查询注入防护:

    // 避免直接使用用户输入构建查询
    // 错误方式
    const query = { username: req.query.username };
    
    // 正确方式 - 使用参数化查询
    const query = { username: sanitize(req.query.username) };
    

十、测试策略

1. 单元测试示例 (使用Jest)

const UserService = require('../services/userService');
const User = require('../models/User');

jest.mock('../models/User');

describe('UserService', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('createUser', () => {
    it('should create a new user', async () => {
      const mockUser = { _id: '1', username: 'test', email: 'test@example.com' };
      User.prototype.save.mockResolvedValue(mockUser);

      const result = await UserService.createUser(mockUser);
      expect(result).toEqual(mockUser);
      expect(User.prototype.save).toHaveBeenCalled();
    });
  });
});

2. 集成测试示例

const request = require('supertest');
const app = require('../app');
const User = require('../models/User');

describe('User API', () => {
  beforeAll(async () => {
    await mongoose.connect(process.env.MONGODB_URI_TEST, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
  });

  afterAll(async () => {
    await mongoose.connection.dropDatabase();
    await mongoose.connection.close();
  });

  describe('POST /api/users', () => {
    it('should create a new user', async () => {
      const res = await request(app)
        .post('/api/users')
        .send({
          username: 'testuser',
          email: 'test@example.com',
          password: 'password123'
        });

      expect(res.statusCode).toEqual(201);
      expect(res.body).toHaveProperty('_id');
      expect(res.body.username).toBe('testuser');
    });
  });
});

十一、部署考虑

  1. 生产环境配置:

    MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/mydatabase?retryWrites=true&w=majority
    NODE_ENV=production
    PORT=8080
    
  2. 连接重试逻辑:

    const mongoose = require('mongoose');
    require('dotenv').config();
    
    const connectWithRetry = () => {
     return mongoose.connect(process.env.MONGODB_URI, {
       useNewUrlParser: true,
       useUnifiedTopology: true,
       retryWrites: true,
       retryReads: true
     })
     .then(() => console.log('Successfully connected to MongoDB'))
     .catch(err => {
       console.error('MongoDB connection error:', err);
       setTimeout(connectWithRetry, 5000);
     });
    };
    
    module.exports = connectWithRetry;
    
  3. 使用PM2进行进程管理:

    npm install pm2 -g
    pm2 start app.js --name "node-mongo-app"
    pm2 save
    pm2 startup
    

十二、监控与日志

  1. 添加请求日志:

    const morgan = require('morgan');
    app.use(morgan('combined'));
    
  2. 性能监控:

    const { monitorMongoose } = require('mongoose-metrics');
    monitorMongoose(mongoose);
    
  3. 错误跟踪:

    const Sentry = require('@sentry/node');
    Sentry.init({ dsn: process.env.SENTRY_DSN });
    app.use(Sentry.Handlers.requestHandler());
    app.use(Sentry.Handlers.errorHandler());
    

总结

本实战指南涵盖了MongoDB与Node.js集成的各个方面,从基础连接到高级特性。关键要点包括:

  1. 使用Mongoose简化数据建模和验证
  2. 实现健壮的CRUD操作
  3. 优化查询性能
  4. 确保应用安全
  5. 建立有效的测试策略
  6. 准备生产环境部署

通过遵循这些实践,您可以构建高性能、可扩展且安全的Node.js应用,充分利用MongoDB的强大功能。